消息
This commit is contained in:
232
TUIKit/components/TUIConversation/actions-menu/index.vue
Normal file
232
TUIKit/components/TUIConversation/actions-menu/index.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<Overlay
|
||||
:maskColor="'transparent'"
|
||||
@onOverlayClick="() => emits('closeConversationActionMenu')"
|
||||
>
|
||||
<div
|
||||
id="conversation-actions-menu"
|
||||
ref="actionsMenuDomRef"
|
||||
:class="[
|
||||
isPC && 'actions-menu-pc',
|
||||
'actions-menu',
|
||||
!isHiddenActionsMenu && 'cancel-hidden',
|
||||
]"
|
||||
:style="{
|
||||
top: `${_actionsMenuPosition.top}px`,
|
||||
left: `${_actionsMenuPosition.left}px`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
:class="['actions-menu-item']"
|
||||
@click.stop="deleteConversation()"
|
||||
>
|
||||
{{ TUITranslateService.t("TUIConversation.删除会话") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!(props.selectedConversation && props.selectedConversation.isPinned)"
|
||||
:class="['actions-menu-item']"
|
||||
@click.stop="handleItem({ name: CONV_OPERATION.ISPINNED })"
|
||||
>
|
||||
{{ TUITranslateService.t("TUIConversation.置顶会话") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectedConversation && props.selectedConversation.isPinned"
|
||||
:class="['actions-menu-item']"
|
||||
@click.stop="handleItem({ name: CONV_OPERATION.DISPINNED })"
|
||||
>
|
||||
{{ TUITranslateService.t("TUIConversation.取消置顶") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!(props.selectedConversation && props.selectedConversation.isMuted)"
|
||||
:class="['actions-menu-item']"
|
||||
@click.stop="handleItem({ name: CONV_OPERATION.MUTE })"
|
||||
>
|
||||
{{ TUITranslateService.t("TUIConversation.消息免打扰") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectedConversation && props.selectedConversation.isMuted"
|
||||
:class="['actions-menu-item']"
|
||||
@click.stop="handleItem({ name: CONV_OPERATION.NOTMUTE })"
|
||||
>
|
||||
{{ TUITranslateService.t("TUIConversation.取消免打扰") }}
|
||||
</div>
|
||||
</div>
|
||||
<Dialog
|
||||
:show="isShowDeleteConversationDialog"
|
||||
:center="true"
|
||||
:isHeaderShow="isPC"
|
||||
@submit="handleItem({ name: CONV_OPERATION.DELETE })"
|
||||
@update:show="updateShowDeleteConversationDialog"
|
||||
>
|
||||
<p class="delDialog-title">
|
||||
{{ TUITranslateService.t(deleteConversationDialogTitle) }}
|
||||
</p>
|
||||
</Dialog>
|
||||
</Overlay>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref,
|
||||
nextTick,
|
||||
onMounted,
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
} from '../../../adapter-vue';
|
||||
import TUIChatEngine, {
|
||||
IConversationModel,
|
||||
TUIStore,
|
||||
TUITranslateService,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { CONV_OPERATION } from '../../../constant';
|
||||
import { isPC, isUniFrameWork } from '../../../utils/env';
|
||||
import Overlay from '../../common/Overlay/index.vue';
|
||||
import Dialog from '../../common/Dialog/index.vue';
|
||||
|
||||
interface IProps {
|
||||
actionsMenuPosition: {
|
||||
top: number;
|
||||
left?: number;
|
||||
conversationHeight?: number;
|
||||
};
|
||||
selectedConversation: IConversationModel | undefined;
|
||||
selectedConversationDomRect: DOMRect | undefined;
|
||||
}
|
||||
|
||||
const emits = defineEmits(['closeConversationActionMenu']);
|
||||
const props = defineProps<IProps>();
|
||||
|
||||
const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance();
|
||||
const actionsMenuDomRef = ref<HTMLElement | null>();
|
||||
const isHiddenActionsMenu = ref(true);
|
||||
const isShowDeleteConversationDialog = ref<boolean>(false);
|
||||
const currentConversation = TUIStore.getConversationModel(
|
||||
props.selectedConversation?.conversationID || '',
|
||||
);
|
||||
const _actionsMenuPosition = ref<{
|
||||
top: number;
|
||||
left?: number;
|
||||
conversationHeight?: number;
|
||||
}>(props.actionsMenuPosition);
|
||||
|
||||
onMounted(() => {
|
||||
checkExceedBounds();
|
||||
});
|
||||
|
||||
const deleteConversationDialogTitle = computed(() => {
|
||||
return props.selectedConversation?.type === TUIChatEngine.TYPES.CONV_C2C
|
||||
? 'TUIConversation.删除后,将清空该聊天的消息记录'
|
||||
: props.selectedConversation?.type === TUIChatEngine.TYPES.CONV_GROUP ? 'TUIConversation.删除后,将清空该群聊的消息记录' : '';
|
||||
});
|
||||
function checkExceedBounds() {
|
||||
// When the component is initially rendered, it executes and self-checks whether the boundary exceeds the screen, and handles it in nextTick.
|
||||
nextTick(() => {
|
||||
if (isUniFrameWork) {
|
||||
// check exceed bounds
|
||||
const query = TUIGlobal?.createSelectorQuery().in(thisInstance);
|
||||
query
|
||||
.select(`#conversation-actions-menu`)
|
||||
.boundingClientRect((data) => {
|
||||
if (data) {
|
||||
// check if actionsMenu is exceed bottom of the screen
|
||||
if (data.bottom > TUIGlobal?.getWindowInfo?.().windowHeight) {
|
||||
_actionsMenuPosition.value = {
|
||||
...props.actionsMenuPosition,
|
||||
top:
|
||||
props.actionsMenuPosition.top
|
||||
- (props.actionsMenuPosition.conversationHeight || 0)
|
||||
- data.height,
|
||||
};
|
||||
}
|
||||
// check if actionsMenu is exceed right of the screen
|
||||
if (_actionsMenuPosition.value.left + data.width + 5 > TUIGlobal.getWindowInfo().windowWidth) {
|
||||
_actionsMenuPosition.value.left = TUIGlobal.getWindowInfo().windowWidth - data.width - 5;
|
||||
}
|
||||
}
|
||||
isHiddenActionsMenu.value = false;
|
||||
})
|
||||
.exec();
|
||||
} else {
|
||||
// Handling the situation where the native Vue menu is lower than the screen
|
||||
const rect = actionsMenuDomRef.value?.getBoundingClientRect();
|
||||
// The PC side sets the position of actionsMenu according to the position of the mouse click, otherwise the default value of 167px is used
|
||||
if (isPC && typeof props.actionsMenuPosition.left !== 'undefined') {
|
||||
_actionsMenuPosition.value.left = props.actionsMenuPosition.left;
|
||||
}
|
||||
if (rect && rect.bottom > window.innerHeight) {
|
||||
_actionsMenuPosition.value.top
|
||||
= props.actionsMenuPosition.top
|
||||
- (props.actionsMenuPosition.conversationHeight || 0)
|
||||
- rect.height;
|
||||
}
|
||||
isHiddenActionsMenu.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleItem = (params: { name: string }) => {
|
||||
const { name } = params;
|
||||
const conversationModel = currentConversation;
|
||||
if (!name || !conversationModel || !conversationModel.conversationID) {
|
||||
return;
|
||||
}
|
||||
switch (name) {
|
||||
case CONV_OPERATION.DELETE:
|
||||
conversationModel?.deleteConversation();
|
||||
break;
|
||||
case CONV_OPERATION.ISPINNED:
|
||||
conversationModel?.pinConversation();
|
||||
break;
|
||||
case CONV_OPERATION.DISPINNED:
|
||||
conversationModel?.pinConversation();
|
||||
break;
|
||||
case CONV_OPERATION.MUTE:
|
||||
conversationModel?.muteConversation();
|
||||
break;
|
||||
case CONV_OPERATION.NOTMUTE:
|
||||
conversationModel?.muteConversation();
|
||||
break;
|
||||
}
|
||||
emits('closeConversationActionMenu');
|
||||
};
|
||||
|
||||
const deleteConversation = () => {
|
||||
isShowDeleteConversationDialog.value = true;
|
||||
};
|
||||
|
||||
const updateShowDeleteConversationDialog = (isShow: boolean) => {
|
||||
if (!isShow) {
|
||||
emits('closeConversationActionMenu');
|
||||
}
|
||||
isShowDeleteConversationDialog.value = isShow;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cancel-hidden {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.actions-menu {
|
||||
position: absolute;
|
||||
left: 164px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
box-shadow: 0 -4px 12px 0 rgba(0, 0, 0, 0.06);
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
|
||||
.actions-menu-item {
|
||||
cursor: pointer;
|
||||
padding: 10px 20px;
|
||||
font-size: 12px;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
&.actions-menu-pc .actions-menu-item:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
import ConversationHeader from './index.vue';
|
||||
|
||||
export default ConversationHeader;
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div
|
||||
:ref="convHeaderRef"
|
||||
class="tui-conversation-header"
|
||||
>
|
||||
<ul
|
||||
v-if="menuList.length > 0"
|
||||
class="list"
|
||||
>
|
||||
<li
|
||||
v-for="(item, index) in menuList"
|
||||
:key="index"
|
||||
class="list-item"
|
||||
>
|
||||
<main
|
||||
class="tui-conversation-header-item"
|
||||
@click.stop="handleMenu(item)"
|
||||
>
|
||||
<Icon
|
||||
v-if="item.icon && !item.data.children"
|
||||
class="tui-conversation-header-item-icon"
|
||||
:file="item.icon"
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="plus"
|
||||
/>
|
||||
<h1 class="tui-conversation-header-item-title">
|
||||
{{ item.text }}
|
||||
</h1>
|
||||
</main>
|
||||
</li>
|
||||
</ul>
|
||||
<ul
|
||||
v-if="showChildren.length > 0"
|
||||
class="tui-conversation-header-children list"
|
||||
>
|
||||
<li
|
||||
v-for="(childrenItem, childrenIndex) in showChildren"
|
||||
:key="childrenIndex"
|
||||
class="list-item"
|
||||
>
|
||||
<main
|
||||
class="tui-conversation-header-item"
|
||||
@click="handleMenu(childrenItem)"
|
||||
>
|
||||
<Icon
|
||||
v-if="childrenItem.icon"
|
||||
class="tui-conversation-header-item-icon"
|
||||
:file="childrenItem.icon"
|
||||
/>
|
||||
<h1 class="tui-conversation-header-item-title">
|
||||
{{ childrenItem.text }}
|
||||
</h1>
|
||||
</main>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, onMounted } from '../../../adapter-vue';
|
||||
import Icon from '../../common/Icon.vue';
|
||||
import Server, { IMenuItem } from './server';
|
||||
|
||||
const showChildren = ref<Array<IMenuItem>>([]);
|
||||
const convHeaderRef = ref<HTMLElement | undefined>();
|
||||
|
||||
const menuList = computed(() => {
|
||||
return Server.getInstance().getMenu();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
showChildren.value = [];
|
||||
});
|
||||
|
||||
const handleMenu = (item: IMenuItem) => {
|
||||
const { data: { children }, listener = { onClicked: () => {} } } = item;
|
||||
if (children) {
|
||||
showChildren.value = showChildren.value.length > 0 ? [] : children;
|
||||
} else {
|
||||
listener.onClicked(item);
|
||||
closeChildren();
|
||||
}
|
||||
};
|
||||
|
||||
const closeChildren = () => {
|
||||
showChildren.value = [];
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
closeChildren,
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="../style/index.scss"></style>
|
||||
@@ -0,0 +1,78 @@
|
||||
import TUICore, { TUIConstants } from '@tencentcloud/tui-core';
|
||||
import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
|
||||
import { isPC } from '../../../utils/env';
|
||||
import createGroup from '../../../assets/icon/start-group.svg';
|
||||
import C2C from '../../../assets/icon/icon-c2c.svg';
|
||||
import { CONV_CREATE_TYPE } from '../../../constant';
|
||||
|
||||
export interface IMenuItem {
|
||||
icon?: string;
|
||||
text: string;
|
||||
data: {
|
||||
name: string;
|
||||
children?: any[];
|
||||
};
|
||||
listener?: {
|
||||
onClicked: (...args: any[]) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export default class ConversationHeaderServer {
|
||||
static instance: ConversationHeaderServer;
|
||||
|
||||
static getInstance(): ConversationHeaderServer {
|
||||
if (!ConversationHeaderServer.instance) {
|
||||
ConversationHeaderServer.instance = new ConversationHeaderServer();
|
||||
}
|
||||
return ConversationHeaderServer.instance;
|
||||
}
|
||||
|
||||
public getMenu(): any[] {
|
||||
const list = this.generateMenuList();
|
||||
if (!isPC && list.length > 0) {
|
||||
return [{
|
||||
text: TUITranslateService.t('TUIConversation.发起会话'),
|
||||
data: {
|
||||
name: 'all',
|
||||
children: list,
|
||||
},
|
||||
}];
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private generateMenuList(): any[] {
|
||||
const list = [
|
||||
{
|
||||
icon: C2C,
|
||||
text: TUITranslateService.t('TUIConversation.发起单聊'),
|
||||
data: {
|
||||
name: CONV_CREATE_TYPE.TYPEC2C,
|
||||
},
|
||||
listener: {
|
||||
onClicked: this.createConversation.bind(this),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: createGroup,
|
||||
text: TUITranslateService.t('TUIConversation.发起群聊'),
|
||||
data: {
|
||||
name: CONV_CREATE_TYPE.TYPEGROUP,
|
||||
},
|
||||
listener: {
|
||||
onClicked: this.createConversation.bind(this),
|
||||
},
|
||||
},
|
||||
];
|
||||
return list;
|
||||
}
|
||||
|
||||
private createConversation(item: IMenuItem) {
|
||||
// Create a conversation and notify conversationServer via TUICore.callService
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUIConversation.SERVICE.NAME,
|
||||
method: TUIConstants.TUIConversation.SERVICE.METHOD.CREATE_CONVERSATION,
|
||||
params: item,
|
||||
});
|
||||
}
|
||||
}
|
||||
310
TUIKit/components/TUIConversation/conversation-list/index.vue
Normal file
310
TUIKit/components/TUIConversation/conversation-list/index.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<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)"
|
||||
@longpress="showConversationActionMenu($event, conversation, index)"
|
||||
@contextmenu="showConversationActionMenu($event, conversation, index, true)"
|
||||
>
|
||||
<aside class="left">
|
||||
<Avatar
|
||||
useSkeletonAnimation
|
||||
:url="conversation.getAvatar()"
|
||||
size="30px"
|
||||
/>
|
||||
<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) => {
|
||||
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;
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,77 @@
|
||||
.tui-conversation {
|
||||
&-item {
|
||||
&-pinned {
|
||||
background: #eef0f3;
|
||||
}
|
||||
|
||||
&-selected,
|
||||
&-toggled {
|
||||
background: rgba(0, 110, 255, 0.1);
|
||||
}
|
||||
|
||||
.left {
|
||||
.num {
|
||||
background: red;
|
||||
color: #fff;
|
||||
|
||||
&-notify {
|
||||
background: red;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-header {
|
||||
&-label {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.middle-box {
|
||||
&-at, &-draft {
|
||||
color: #fb5059 !important;
|
||||
font-family: PingFangSC-Regular;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&-content {
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content-footer {
|
||||
color: #999;
|
||||
|
||||
.time {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
.dialog {
|
||||
background: #fff;
|
||||
|
||||
&-item {
|
||||
background: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
box-shadow: 0 -4px 12px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.conversation-options {
|
||||
font-family: PingFangSC-Regular;
|
||||
font-weight: 400;
|
||||
color: #4f4f4f;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
.tui-conversation-list-h5 {
|
||||
.tui-conversation-content {
|
||||
.dialog {
|
||||
left: auto;
|
||||
right: 18px;
|
||||
padding: 0;
|
||||
|
||||
.conversation-options {
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&-item-up {
|
||||
top: -70px;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-conversation-item {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
.content {
|
||||
.name {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.middle-box {
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@import '../../../../assets/styles/common';
|
||||
@import './color';
|
||||
@import './web';
|
||||
@import './h5';
|
||||
@@ -0,0 +1,186 @@
|
||||
.tui-conversation-list {
|
||||
font-family: PingFangSC-Regular;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.tui-conversation {
|
||||
&-item {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.left {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
.num {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: 0;
|
||||
top: -5px;
|
||||
min-width: 10px;
|
||||
width: fit-content;
|
||||
padding: 0 2.5px;
|
||||
height: 15px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
line-height: 15px;
|
||||
border-radius: 7.5px;
|
||||
}
|
||||
|
||||
.num-notify {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: 2px;
|
||||
top: -2px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
line-height: 15px;
|
||||
border-radius: 65%;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.online-status {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
left: 24px;
|
||||
top: 22px;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
|
||||
&-online {
|
||||
background: #29cc85;
|
||||
}
|
||||
|
||||
&-offline {
|
||||
background: #a4a4a4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-footer {
|
||||
line-height: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding-left: 8px;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.content-footer {
|
||||
align-items: flex-end;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-header {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.name {
|
||||
width: 110px;
|
||||
letter-spacing: 0;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.middle-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-at,
|
||||
&-draft {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
position: relative;
|
||||
|
||||
.tui-conversation-item:hover {
|
||||
background: rgba(0, 110, 255, 0.1);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
padding: 2px 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&-item {
|
||||
top: 30px;
|
||||
left: 164px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.conversation-options {
|
||||
padding: 5px 0;
|
||||
height: 17px;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
&-item-up {
|
||||
top: -50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import ConversationNetwork from './index.vue';
|
||||
|
||||
export default ConversationNetwork;
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="isNotNetwork"
|
||||
class="network"
|
||||
>
|
||||
<i class="icon icon-error">!</i>
|
||||
<p class="network-content">
|
||||
{{
|
||||
TUITranslateService.t("TUIConversation.网络异常,请您检查网络设置")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TUIChatEngine, {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUITranslateService,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import {
|
||||
ref,
|
||||
} from '../../../adapter-vue';
|
||||
|
||||
const isNotNetwork = ref(false);
|
||||
|
||||
TUIStore.watch(StoreName.USER, {
|
||||
netStateChange: (value: string) => {
|
||||
isNotNetwork.value = (value === TUIChatEngine.TYPES.NET_STATE_DISCONNECTED);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="../style/index.scss"></style>
|
||||
2
TUIKit/components/TUIConversation/entry.ts
Normal file
2
TUIKit/components/TUIConversation/entry.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import { TUIChatKit } from '../../index.ts';
|
||||
TUIChatKit?.init(); // Add optional chaining operator to fix sample main package integration errors
|
||||
5
TUIKit/components/TUIConversation/index.ts
Normal file
5
TUIKit/components/TUIConversation/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import TUIConversation from "./index.vue";
|
||||
import TUIConversationServer from "./server";
|
||||
new TUIConversationServer();
|
||||
|
||||
export default TUIConversation;
|
||||
110
TUIKit/components/TUIConversation/index.vue
Normal file
110
TUIKit/components/TUIConversation/index.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div
|
||||
class="tui-conversation"
|
||||
@click="handleClickConv"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd"
|
||||
>
|
||||
<TUISearch searchType="global" />
|
||||
<ConversationHeader
|
||||
v-if="isShowConversationHeader"
|
||||
ref="headerRef"
|
||||
/>
|
||||
<ConversationNetwork />
|
||||
<ConversationList
|
||||
ref="conversationListDomRef"
|
||||
class="tui-conversation-list"
|
||||
@handleSwitchConversation="handleSwitchConversation"
|
||||
@getPassingRef="getPassingRef"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { TUIStore, StoreName } from '@tencentcloud/chat-uikit-engine';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { ref } from '../../adapter-vue';
|
||||
import TUISearch from '../TUISearch/index.vue';
|
||||
import ConversationList from './conversation-list/index.vue';
|
||||
import ConversationHeader from './conversation-header/index.vue';
|
||||
import ConversationNetwork from './conversation-network/index.vue';
|
||||
import { onHide } from '@dcloudio/uni-app';
|
||||
// #ifdef MP-WEIXIN
|
||||
// uniapp packaged mini-programs are integrated by default, and the default initialization entry file is imported here
|
||||
// TUIChatKit init needs to be encapsulated because uni vue2 will report an error when compiling H5 directly through conditional compilation
|
||||
import './entry.ts';
|
||||
// #endif
|
||||
|
||||
const emits = defineEmits(['handleSwitchConversation']);
|
||||
|
||||
const totalUnreadCount = ref(0);
|
||||
const headerRef = ref<typeof ConversationHeader>();
|
||||
const conversationListDomRef = ref<typeof ConversationList>();
|
||||
const touchX = ref<number>(0);
|
||||
const touchY = ref<number>(0);
|
||||
const isShowConversationHeader = ref<boolean>(true);
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
totalUnreadCount: (count: number) => {
|
||||
totalUnreadCount.value = count;
|
||||
},
|
||||
});
|
||||
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
isShowConversationHeader: (showStatus: boolean) => {
|
||||
isShowConversationHeader.value = showStatus !== false;
|
||||
},
|
||||
});
|
||||
|
||||
const handleSwitchConversation = (conversationID: string) => {
|
||||
TUIGlobal?.navigateTo({
|
||||
url: '/TUIKit/components/TUIChat/index',
|
||||
});
|
||||
emits('handleSwitchConversation', conversationID);
|
||||
};
|
||||
|
||||
const closeChildren = () => {
|
||||
headerRef?.value?.closeChildren();
|
||||
conversationListDomRef?.value?.closeChildren();
|
||||
};
|
||||
|
||||
const handleClickConv = () => {
|
||||
closeChildren();
|
||||
};
|
||||
|
||||
onHide(closeChildren);
|
||||
|
||||
const handleTouchStart = (e: any) => {
|
||||
touchX.value = e.changedTouches[0].clientX;
|
||||
touchY.value = e.changedTouches[0].clientY;
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e: any) => {
|
||||
const x = e.changedTouches[0].clientX;
|
||||
const y = e.changedTouches[0].clientY;
|
||||
let turn = '';
|
||||
if (x - touchX.value > 50 && Math.abs(y - touchY.value) < 50) {
|
||||
// Swipe right
|
||||
turn = 'right';
|
||||
} else if (x - touchX.value < -50 && Math.abs(y - touchY.value) < 50) {
|
||||
// Swipe left
|
||||
turn = 'left';
|
||||
}
|
||||
if (y - touchY.value > 50 && Math.abs(x - touchX.value) < 50) {
|
||||
// Swipe down
|
||||
turn = 'down';
|
||||
} else if (y - touchY.value < -50 && Math.abs(x - touchX.value) < 50) {
|
||||
// Swipe up
|
||||
turn = 'up';
|
||||
}
|
||||
// Operate according to the direction
|
||||
if (turn === 'down' || turn === 'up') {
|
||||
closeChildren();
|
||||
}
|
||||
};
|
||||
|
||||
const getPassingRef = (ref) => {
|
||||
ref.value = conversationListDomRef.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
163
TUIKit/components/TUIConversation/server.ts
Normal file
163
TUIKit/components/TUIConversation/server.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import TUICore, { TUIConstants } from '@tencentcloud/tui-core';
|
||||
import {
|
||||
TUITranslateService,
|
||||
TUIConversationService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
} from '@tencentcloud/chat-uikit-engine';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { CONV_CREATE_TYPE } from '../../constant';
|
||||
import { isUniFrameWork } from '../../utils/env';
|
||||
import createGroupIcon from '../../assets/icon/start-group.svg';
|
||||
import createC2CIcon from '../../assets/icon/icon-c2c.svg';
|
||||
import { enableSampleTaskStatus } from '../../utils/enableSampleTaskStatus';
|
||||
|
||||
export default class TUIConversationServer {
|
||||
static instance: TUIConversationServer;
|
||||
private onCallParamsMap: Map<string, any>;
|
||||
private onCallCallbackMap: Map<string, () => void>;
|
||||
public constants: typeof TUIConstants;
|
||||
constructor() {
|
||||
TUICore.registerService(TUIConstants.TUIConversation.SERVICE.NAME, this);
|
||||
TUICore.registerExtension(TUIConstants.TUISearch.EXTENSION.SEARCH_MORE.EXT_ID, this);
|
||||
this.onCallParamsMap = new Map();
|
||||
this.onCallCallbackMap = new Map();
|
||||
this.constants = TUIConstants;
|
||||
}
|
||||
|
||||
static getInstance(): TUIConversationServer {
|
||||
if (!TUIConversationServer.instance) {
|
||||
TUIConversationServer.instance = new TUIConversationServer();
|
||||
}
|
||||
return TUIConversationServer.instance;
|
||||
}
|
||||
|
||||
public getOnCallParams(method: string): any {
|
||||
return this.onCallParamsMap.get(method);
|
||||
}
|
||||
|
||||
public getOnCallCallback(method: string): (() => void) | undefined {
|
||||
return this.onCallCallbackMap.get(method);
|
||||
}
|
||||
|
||||
public onCall(method: string, params: Record<string, any>, callback: () => void): void {
|
||||
this.onCallParamsMap.set(method, params);
|
||||
this.onCallCallbackMap.set(method, callback);
|
||||
switch (method) {
|
||||
case TUIConstants.TUIConversation.SERVICE.METHOD.CREATE_CONVERSATION:
|
||||
this.createConversation(params);
|
||||
break;
|
||||
case TUIConstants.TUIConversation.SERVICE.METHOD.HIDE_CONVERSATION_HEADER:
|
||||
this.hideConversationHeader();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public onGetExtension(extensionID: string) {
|
||||
if (extensionID === TUIConstants.TUISearch.EXTENSION.SEARCH_MORE.EXT_ID) {
|
||||
const list = [
|
||||
{
|
||||
weight: 100,
|
||||
icon: createC2CIcon,
|
||||
text: TUITranslateService.t('TUIConversation.发起单聊'),
|
||||
data: {
|
||||
name: CONV_CREATE_TYPE.TYPEC2C,
|
||||
},
|
||||
listener: {
|
||||
onClicked: this.createConversation.bind(this),
|
||||
},
|
||||
},
|
||||
{
|
||||
weight: 100,
|
||||
icon: createGroupIcon,
|
||||
text: TUITranslateService.t('TUIConversation.发起群聊'),
|
||||
data: {
|
||||
name: CONV_CREATE_TYPE.TYPEGROUP,
|
||||
},
|
||||
listener: {
|
||||
onClicked: this.createConversation.bind(this),
|
||||
},
|
||||
},
|
||||
];
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
private createConversation(item: any) {
|
||||
// Tell TUIContact to call the SelectFriend component to select a friend
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUIContact.SERVICE.NAME,
|
||||
method: TUIConstants.TUIContact.SERVICE.METHOD.SELECT_FRIEND,
|
||||
params: {
|
||||
title: item.text,
|
||||
isRadio: item.data.name !== CONV_CREATE_TYPE.TYPEGROUP,
|
||||
isNeedSearch: !TUIStore.getData(StoreName.APP, 'isOfficial'),
|
||||
},
|
||||
callback: async (memberList: any[]) => {
|
||||
if (!memberList || memberList.length === 0) {
|
||||
// Return to the previous page
|
||||
return this.routerForward(null);
|
||||
}
|
||||
if (item.data.name === CONV_CREATE_TYPE.TYPEGROUP) {
|
||||
// After selecting members, if you want to create a group chat, you need to create a group
|
||||
this.createGroup(memberList);
|
||||
} else {
|
||||
const { userID } = memberList[0];
|
||||
// Generate Conversation
|
||||
await this.generateConversation(`C2C${userID}`);
|
||||
this.routerForward(`C2C${userID}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private createGroup(memberList: any[]) {
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUIGroup.SERVICE.NAME,
|
||||
method: TUIConstants.TUIGroup.SERVICE.METHOD.CREATE_GROUP,
|
||||
params: {
|
||||
title: TUITranslateService.t('TUIConversation.发起群聊'),
|
||||
memberList,
|
||||
},
|
||||
callback: async (group: any) => {
|
||||
let conversationID: string | null = null;
|
||||
if (group) {
|
||||
const { groupID } = group;
|
||||
await this.generateConversation(`GROUP${groupID}`);
|
||||
conversationID = `GROUP${groupID}`;
|
||||
}
|
||||
this.routerForward(conversationID);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async routerForward(conversationID: string | null): Promise<void> {
|
||||
if (isUniFrameWork) {
|
||||
await TUIGlobal?.reLaunch({
|
||||
url: '/TUIKit/components/TUIConversation/index',
|
||||
});
|
||||
if (conversationID) {
|
||||
TUIGlobal?.navigateTo({
|
||||
url: '/TUIKit/components/TUIChat/index',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private generateConversation(conversationID: string) {
|
||||
TUIConversationService.switchConversation(conversationID)
|
||||
.then(() => {
|
||||
if (conversationID.startsWith('GROUP')) {
|
||||
enableSampleTaskStatus('groupChat');
|
||||
}
|
||||
console.warn('打开会话成功');
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.warn('打开会话失败', err.code, err.msg);
|
||||
});
|
||||
}
|
||||
|
||||
private hideConversationHeader = () => {
|
||||
TUIStore.update(StoreName.CUSTOM, 'isShowConversationHeader', false);
|
||||
};
|
||||
}
|
||||
12
TUIKit/components/TUIConversation/style/color.scss
Normal file
12
TUIKit/components/TUIConversation/style/color.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
.tui-conversation {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.network {
|
||||
&-content {
|
||||
font-family: PingFangSC-Regular;
|
||||
font-weight: 400;
|
||||
color: #e54545;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
3
TUIKit/components/TUIConversation/style/index.scss
Normal file
3
TUIKit/components/TUIConversation/style/index.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import '../../../assets/styles/common';
|
||||
@import './web';
|
||||
@import './color';
|
||||
107
TUIKit/components/TUIConversation/style/web.scss
Normal file
107
TUIKit/components/TUIConversation/style/web.scss
Normal file
@@ -0,0 +1,107 @@
|
||||
.tui-conversation {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-list {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-conversation-header {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #F4F5F9;
|
||||
padding: 7px 0;
|
||||
|
||||
&-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
padding: 7px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-item{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-title {
|
||||
padding: 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&-children {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
z-index: 3;
|
||||
padding: 7px 9px;
|
||||
border-bottom: none;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 3px 7px 0 #0003;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.network {
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon-error{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 15px;
|
||||
background: red;
|
||||
color: #fff;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
&-content {
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.plus {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.plus::before,
|
||||
.plus::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: #232832;
|
||||
border-radius: 0.5px;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.plus::after {
|
||||
transform: rotate(90deg);
|
||||
width: 0.5px;
|
||||
}
|
||||
Reference in New Issue
Block a user