添加红包功能

This commit is contained in:
bobobobo
2026-01-07 00:52:23 +08:00
parent 95b46d5cf4
commit 1c021cdd21
12 changed files with 1542 additions and 1346 deletions

View File

@@ -3,17 +3,12 @@
<Navigation :title="title">
<template #left>
<div @click="back">
<Icon
:file="backSVG"
/>
<Icon :file="backSVG" />
</div>
</template>
<template #right>
<div @click="onNavigationBarButtonTap">
<Icon
v-if="isGroup"
:file="More"
/>
<Icon v-if="isGroup" :file="More" />
</div>
</template>
</Navigation>
@@ -21,78 +16,78 @@
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from '../../../adapter-vue';
import {
TUIStore,
StoreName,
IConversationModel,
TUITranslateService,
} from '@tencentcloud/chat-uikit-engine-lite';
import { onLoad } from '@dcloudio/uni-app';
import Navigation from '../../common/Navigation/index.vue';
import Icon from '../../common/Icon.vue';
import More from '../../../assets/icon/more.svg';
import backSVG from '../../../assets/icon/back.svg';
import { onMounted, onUnmounted, ref } from '../../../adapter-vue'
import {
TUIStore,
StoreName,
IConversationModel,
TUITranslateService
} from '@tencentcloud/chat-uikit-engine-lite'
import { onLoad } from '@dcloudio/uni-app'
import Navigation from '../../common/Navigation/index.vue'
import Icon from '../../common/Icon.vue'
import More from '../../../assets/icon/more.svg'
import backSVG from '../../../assets/icon/back.svg'
const emits = defineEmits(['openGroupManagement']);
const props = defineProps(['isGroup']);
const emits = defineEmits(['openGroupManagement'])
const props = defineProps(['isGroup'])
const currentConversation = ref<IConversationModel>();
const typingStatus = ref(false);
const title = ref('');
const currentConversation = ref<IConversationModel>()
const typingStatus = ref(false)
const title = ref('')
const setChatHeaderContent = (content: string) => {
title.value = content || '云通信 IM';
};
const onNavigationBarButtonTap = () => {
if (props.isGroup) {
emits('openGroupManagement');
const setChatHeaderContent = (content: string) => {
title.value = content || '云通信 IM'
}
};
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.watch(StoreName.CHAT, {
typingStatus: onTypingStatusUpdated,
});
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.unwatch(StoreName.CHAT, {
typingStatus: onTypingStatusUpdated,
});
});
onLoad(() => {
setChatHeaderContent(currentConversation.value?.getShowName());
});
function onCurrentConversationUpdated(conversation: IConversationModel) {
currentConversation.value = conversation;
if (!typingStatus.value) {
setChatHeaderContent(currentConversation?.value?.getShowName());
const onNavigationBarButtonTap = () => {
if (props.isGroup) {
emits('openGroupManagement')
}
}
}
function onTypingStatusUpdated(status: boolean) {
typingStatus.value = status;
if (typingStatus.value) {
setChatHeaderContent(TUITranslateService.t('TUIChat.对方正在输入'));
} else {
setChatHeaderContent(currentConversation.value?.getShowName());
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated
})
TUIStore.watch(StoreName.CHAT, {
typingStatus: onTypingStatusUpdated
})
})
onUnmounted(() => {
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated
})
TUIStore.unwatch(StoreName.CHAT, {
typingStatus: onTypingStatusUpdated
})
})
onLoad(() => {
setChatHeaderContent(currentConversation.value?.getShowName())
})
function onCurrentConversationUpdated(
conversation: IConversationModel
) {
currentConversation.value = conversation
if (!typingStatus.value) {
setChatHeaderContent(currentConversation?.value?.getShowName())
}
}
}
function back() {
uni.navigateBack();
}
function onTypingStatusUpdated(status: boolean) {
typingStatus.value = status
if (typingStatus.value) {
setChatHeaderContent(TUITranslateService.t('TUIChat.对方正在输入'))
} else {
setChatHeaderContent(currentConversation.value?.getShowName())
}
}
function back() {
uni.navigateBack()
}
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View File

@@ -3,7 +3,7 @@
:class="[
'message-input-toolbar',
'message-input-toolbar-h5',
'message-input-toolbar-uni',
'message-input-toolbar-uni'
]"
>
<div v-if="props.displayType === 'emojiPicker'">
@@ -20,18 +20,17 @@
:class="[
'message-input-toolbar-list',
'message-input-toolbar-h5-list',
'message-input-toolbar-uni-list',
'message-input-toolbar-uni-list'
]"
>
<CameraUpload
v-if="featureConfig.InputCamera"
/>
<AlbumUpload
v-if="featureConfig.InputAlbum"
/>
<CameraUpload v-if="featureConfig.InputCamera" />
<AlbumUpload v-if="featureConfig.InputAlbum" />
<template v-if="currentExtensionList.length > 0">
<div
v-for="(extension, index) in currentExtensionList.slice(0, slicePos)"
v-for="(extension, index) in currentExtensionList.slice(
0,
slicePos
)"
:key="index"
>
<ToolbarItemContainer
@@ -64,6 +63,7 @@
v-if="featureConfig.InputEvaluation"
@onDialogPopupShowOrHide="handleSwiperDotShow"
/>
<RedEnvelope />
</template>
</swiper-item>
<swiper-item
@@ -71,11 +71,13 @@
:class="[
'message-input-toolbar-list',
'message-input-toolbar-h5-list',
'message-input-toolbar-uni-list',
'message-input-toolbar-uni-list'
]"
>
<div
v-for="(extension, index) in currentExtensionList.slice(slicePos)"
v-for="(extension, index) in currentExtensionList.slice(
slicePos
)"
:key="index"
>
<ToolbarItemContainer
@@ -117,188 +119,217 @@
</div>
</template>
<script setup lang="ts">
import TUIChatEngine, {
IConversationModel,
TUIStore,
StoreName,
TUIReportService,
} from '@tencentcloud/chat-uikit-engine-lite';
import TUICore, { ExtensionInfo, TUIConstants } from '@tencentcloud/tui-core-lite';
import { ref, onUnmounted, onMounted } from '../../../adapter-vue';
import AlbumUpload from './album-upload/index.vue';
import CameraUpload from './camera-upload/index.vue';
import Evaluate from './evaluate/index.vue';
import Words from './words/index.vue';
import ToolbarItemContainer from './toolbar-item-container/index.vue';
import EmojiPickerDialog from './emoji-picker/emoji-picker-dialog.vue';
import UserSelector from './user-selector/index.vue';
import TUIChatConfig from '../config';
import { enableSampleTaskStatus } from '../../../utils/enableSampleTaskStatus';
import { ToolbarDisplayType } from '../../../interface';
import OfflinePushInfoManager, { PUSH_SCENE } from '../offlinePushInfoManager/index';
import TUIChatEngine, {
IConversationModel,
TUIStore,
StoreName,
TUIReportService
} from '@tencentcloud/chat-uikit-engine-lite'
import TUICore, {
ExtensionInfo,
TUIConstants
} from '@tencentcloud/tui-core-lite'
import { ref, onUnmounted, onMounted } from '../../../adapter-vue'
import AlbumUpload from './album-upload/index.vue'
import CameraUpload from './camera-upload/index.vue'
import Evaluate from './evaluate/index.vue'
import RedEnvelope from './red-envelope/index.vue'
import Words from './words/index.vue'
import ToolbarItemContainer from './toolbar-item-container/index.vue'
import EmojiPickerDialog from './emoji-picker/emoji-picker-dialog.vue'
import UserSelector from './user-selector/index.vue'
import TUIChatConfig from '../config'
import { enableSampleTaskStatus } from '../../../utils/enableSampleTaskStatus'
import { ToolbarDisplayType } from '../../../interface'
import OfflinePushInfoManager, {
PUSH_SCENE
} from '../offlinePushInfoManager/index'
interface IProps {
displayType: ToolbarDisplayType;
}
const props = withDefaults(defineProps<IProps>(), {
});
const emits = defineEmits(['changeToolbarDisplayType']);
const currentConversation = ref<IConversationModel>();
const isGroup = ref<boolean>(false);
const selectorShowType = ref<string>('');
const userSelectorRef = ref();
const currentUserSelectorExtension = ref<ExtensionInfo | null>();
const currentExtensionList = ref<ExtensionInfo[]>([]);
const isSwiperIndicatorDotsEnable = ref<boolean>(false);
const featureConfig = TUIChatConfig.getFeatureConfig();
const neededCountFirstPage = ref<number>(8);
const slicePos = ref<number>(0);
const computeToolbarPaging = () => {
if (featureConfig.InputAlbum && featureConfig.InputCamera) {
neededCountFirstPage.value -= 2;
} else if (featureConfig.InputAlbum || featureConfig.InputCamera) {
neededCountFirstPage.value -= 1;
interface IProps {
displayType: ToolbarDisplayType
}
slicePos.value = neededCountFirstPage.value;
neededCountFirstPage.value -= currentExtensionList.value.length;
const props = withDefaults(defineProps<IProps>(), {})
if (neededCountFirstPage.value === 1) {
isSwiperIndicatorDotsEnable.value = (featureConfig.InputEvaluation && featureConfig.InputQuickReplies);
} else if (neededCountFirstPage.value < 1) {
isSwiperIndicatorDotsEnable.value = featureConfig.InputEvaluation || featureConfig.InputQuickReplies;
}
};
const emits = defineEmits(['changeToolbarDisplayType'])
onMounted(() => {
TUIStore.watch(StoreName.CUSTOM, {
activeConversation: onActiveConversationUpdate,
});
});
const currentConversation = ref<IConversationModel>()
const isGroup = ref<boolean>(false)
const selectorShowType = ref<string>('')
const userSelectorRef = ref()
const currentUserSelectorExtension = ref<ExtensionInfo | null>()
const currentExtensionList = ref<ExtensionInfo[]>([])
const isSwiperIndicatorDotsEnable = ref<boolean>(false)
const featureConfig = TUIChatConfig.getFeatureConfig()
const neededCountFirstPage = ref<number>(8)
const slicePos = ref<number>(0)
onUnmounted(() => {
TUIStore.unwatch(StoreName.CUSTOM, {
activeConversation: onActiveConversationUpdate,
});
});
const onActiveConversationUpdate = (conversationID: string) => {
if (!conversationID) {
return;
}
if (conversationID !== currentConversation.value?.conversationID) {
getExtensionList();
computeToolbarPaging();
currentConversation.value = TUIStore.getData(StoreName.CONV, 'currentConversation');
isGroup.value = conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP);
}
};
const getExtensionList = () => {
const chatType = TUIChatConfig.getChatType();
const params: Record<string, boolean | string> = { chatType };
// Backward compatibility: When callkit does not have chatType judgment, use filterVoice and filterVideo to filter
if (chatType === TUIConstants.TUIChat.TYPE.CUSTOMER_SERVICE) {
params.filterVoice = true;
params.filterVideo = true;
enableSampleTaskStatus('customerService');
}
// uni-app build ios app has null in last index need to filter
currentExtensionList.value = [
...TUICore.getExtensionList(TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID, params),
].filter((extension: ExtensionInfo) => {
if (extension?.data?.name === 'search') {
return featureConfig.MessageSearch;
const computeToolbarPaging = () => {
if (featureConfig.InputAlbum && featureConfig.InputCamera) {
neededCountFirstPage.value -= 2
} else if (featureConfig.InputAlbum || featureConfig.InputCamera) {
neededCountFirstPage.value -= 1
}
return true;
});
reportExtension(currentExtensionList.value);
};
function reportExtension(extensionList: ExtensionInfo[]) {
extensionList.forEach((extension: ExtensionInfo) => {
const _name = extension?.data?.name;
if (_name === 'voiceCall') {
TUIReportService.reportFeature(203, 'voice-call');
} else if (_name === 'videoCall') {
TUIReportService.reportFeature(203, 'video-call');
} else if (_name === 'quickRoom') {
TUIReportService.reportFeature(204);
slicePos.value = neededCountFirstPage.value
neededCountFirstPage.value -= currentExtensionList.value.length
if (neededCountFirstPage.value === 1) {
isSwiperIndicatorDotsEnable.value =
featureConfig.InputEvaluation && featureConfig.InputQuickReplies
} else if (neededCountFirstPage.value < 1) {
isSwiperIndicatorDotsEnable.value =
featureConfig.InputEvaluation || featureConfig.InputQuickReplies
}
});
}
// handle extensions onclick
const onExtensionClick = (extension: ExtensionInfo) => {
// uniapp vue2 build wx lose listener proto
const extensionModel = currentExtensionList.value.find(
targetExtension => targetExtension?.data?.name === extension?.data?.name,
);
switch (extensionModel?.data?.name) {
case 'voiceCall':
onCallExtensionClicked(extensionModel, 1);
break;
case 'videoCall':
onCallExtensionClicked(extensionModel, 2);
break;
case 'search':
extensionModel?.listener?.onClicked?.();
break;
default:
break;
}
};
const onCallExtensionClicked = (extension: ExtensionInfo, callType: number) => {
selectorShowType.value = extension?.data?.name;
if (currentConversation?.value?.type === TUIChatEngine.TYPES.CONV_C2C) {
extension?.listener?.onClicked?.({
userIDList: [currentConversation?.value?.conversationID?.slice(3)],
type: callType,
onMounted(() => {
TUIStore.watch(StoreName.CUSTOM, {
activeConversation: onActiveConversationUpdate
})
})
onUnmounted(() => {
TUIStore.unwatch(StoreName.CUSTOM, {
activeConversation: onActiveConversationUpdate
})
})
const onActiveConversationUpdate = (conversationID: string) => {
if (!conversationID) {
return
}
if (conversationID !== currentConversation.value?.conversationID) {
getExtensionList()
computeToolbarPaging()
currentConversation.value = TUIStore.getData(
StoreName.CONV,
'currentConversation'
)
isGroup.value = conversationID.startsWith(
TUIChatEngine.TYPES.CONV_GROUP
)
}
}
const getExtensionList = () => {
const chatType = TUIChatConfig.getChatType()
const params: Record<string, boolean | string> = { chatType }
// Backward compatibility: When callkit does not have chatType judgment, use filterVoice and filterVideo to filter
if (chatType === TUIConstants.TUIChat.TYPE.CUSTOMER_SERVICE) {
params.filterVoice = true
params.filterVideo = true
enableSampleTaskStatus('customerService')
}
// uni-app build ios app has null in last index need to filter
currentExtensionList.value = [
...TUICore.getExtensionList(
TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID,
params
)
].filter((extension: ExtensionInfo) => {
if (extension?.data?.name === 'search') {
return featureConfig.MessageSearch
}
return true
})
reportExtension(currentExtensionList.value)
}
function reportExtension(extensionList: ExtensionInfo[]) {
extensionList.forEach((extension: ExtensionInfo) => {
const _name = extension?.data?.name
if (_name === 'voiceCall') {
TUIReportService.reportFeature(203, 'voice-call')
} else if (_name === 'videoCall') {
TUIReportService.reportFeature(203, 'video-call')
} else if (_name === 'quickRoom') {
TUIReportService.reportFeature(204)
}
})
}
// handle extensions onclick
const onExtensionClick = (extension: ExtensionInfo) => {
// uniapp vue2 build wx lose listener proto
const extensionModel = currentExtensionList.value.find(
targetExtension =>
targetExtension?.data?.name === extension?.data?.name
)
switch (extensionModel?.data?.name) {
case 'voiceCall':
onCallExtensionClicked(extensionModel, 1)
break
case 'videoCall':
onCallExtensionClicked(extensionModel, 2)
break
case 'search':
extensionModel?.listener?.onClicked?.()
break
default:
break
}
}
const onCallExtensionClicked = (
extension: ExtensionInfo,
callType: number
) => {
selectorShowType.value = extension?.data?.name
if (
currentConversation?.value?.type === TUIChatEngine.TYPES.CONV_C2C
) {
extension?.listener?.onClicked?.({
userIDList: [
currentConversation?.value?.conversationID?.slice(3)
],
type: callType,
callParams: {
offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(
PUSH_SCENE.CALL
)
}
})
} else if (isGroup.value) {
currentUserSelectorExtension.value = extension
userSelectorRef?.value?.toggleShow &&
userSelectorRef.value.toggleShow(true)
}
}
const genExtensionIcon = (extension: any) => extension?.icon
const genExtensionText = (extension: any) => extension?.text
const onUserSelectorSubmit = (selectedInfo: any) => {
currentUserSelectorExtension.value?.listener?.onClicked?.({
...selectedInfo,
callParams: {
offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(PUSH_SCENE.CALL),
},
});
} else if (isGroup.value) {
currentUserSelectorExtension.value = extension;
userSelectorRef?.value?.toggleShow && userSelectorRef.value.toggleShow(true);
offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(
PUSH_SCENE.CALL
)
}
})
currentUserSelectorExtension.value = null
}
};
const genExtensionIcon = (extension: any) => extension?.icon;
const genExtensionText = (extension: any) => extension?.text;
const onUserSelectorCancel = () => {
currentUserSelectorExtension.value = null
}
const onUserSelectorSubmit = (selectedInfo: any) => {
currentUserSelectorExtension.value?.listener?.onClicked?.({
...selectedInfo,
callParams: {
offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(PUSH_SCENE.CALL),
},
});
currentUserSelectorExtension.value = null;
};
const onUserSelectorCancel = () => {
currentUserSelectorExtension.value = null;
};
const handleSwiperDotShow = (showStatus: boolean) => {
isSwiperIndicatorDotsEnable.value = (neededCountFirstPage.value <= 1 && !showStatus);
emits('changeToolbarDisplayType', showStatus ? 'dialog' : 'tools');
};
const handleSwiperDotShow = (showStatus: boolean) => {
isSwiperIndicatorDotsEnable.value =
neededCountFirstPage.value <= 1 && !showStatus
emits('changeToolbarDisplayType', showStatus ? 'dialog' : 'tools')
}
</script>
<script lang="ts">
export default {
options: {
styleIsolation: 'shared',
},
};
export default {
options: {
styleIsolation: 'shared'
}
}
</script>
<style lang="scss">
@import '../../../assets/styles/common';
@import './style/uni';
@import '../../../assets/styles/common';
@import './style/uni';
</style>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import ToolbarItemContainer from '../toolbar-item-container/index.vue'
import custom from '../../../../assets/icon/red-packet.svg'
import { isUniFrameWork } from '../../../../utils/env'
const evaluateIcon = custom;
</script>
<template>
<ToolbarItemContainer
:iconFile="evaluateIcon"
:iconWidth="isUniFrameWork ? '26px' : '20px'"
:iconHeight="isUniFrameWork ? '26px' : '20px'"
title="红包"
>
<view>弹出窗口</view>
</ToolbarItemContainer>
</template>
<style scoped lang="scss"></style>

View File

@@ -5,172 +5,175 @@ import TUIChatEngine, {
TUIUserService,
TUITranslateService,
AddFriendParams,
JoinGroupParams,
} from '@tencentcloud/chat-uikit-engine-lite';
import { TUIGlobal } from '@tencentcloud/universal-api';
import { Toast, TOAST_TYPE } from '../../common/Toast/index';
JoinGroupParams
} from '@tencentcloud/chat-uikit-engine-lite'
import { TUIGlobal } from '@tencentcloud/universal-api'
import { Toast, TOAST_TYPE } from '../../common/Toast/index'
import { deleteImGroup, quitImGroup } from '../../../../api/tui-kit'
import { switchTab } from '../../../../utils/router'
export const generateAvatar = (item: any): string => {
return (
item?.avatar
|| item?.profile?.avatar
|| (item?.groupID && 'https://web.sdk.qcloud.com/im/assets/images/Public.svg')
|| 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
);
};
item?.avatar ||
item?.profile?.avatar ||
(item?.groupID &&
'https://web.sdk.qcloud.com/im/assets/images/Public.svg') ||
'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
)
}
export const generateName = (item: any): string => {
return (
item?.remark
|| item?.name
|| item?.profile?.nick
|| item?.nick
|| item?.groupID
|| item?.userID
|| ''
);
};
item?.remark ||
item?.name ||
item?.profile?.nick ||
item?.nick ||
item?.groupID ||
item?.userID ||
''
)
}
export const generateContactInfoName = (item: any): string => {
return (
item?.name
|| item?.profile?.nick
|| item?.nick
|| item?.groupID
|| item?.userID
|| ''
);
};
item?.name ||
item?.profile?.nick ||
item?.nick ||
item?.groupID ||
item?.userID ||
''
)
}
// Parse the basic information display content of the contactInfo module
// Group information display: group ID group type
// User information display: user ID personal signature
export const generateContactInfoBasic = (
contactInfo: any,
): any[] => {
export const generateContactInfoBasic = (contactInfo: any): any[] => {
const res = [
{
label: contactInfo?.groupID ? '群ID' : 'ID',
data: contactInfo?.groupID || contactInfo?.userID || '',
},
];
data: contactInfo?.groupID || contactInfo?.userID || ''
}
]
if (!isApplicationType(contactInfo)) {
res.push({
label: contactInfo?.groupID ? '群类型' : '个性签名',
data: contactInfo?.type || contactInfo?.profile?.selfSignature || '',
});
data: contactInfo?.type || contactInfo?.profile?.selfSignature || ''
})
}
return res;
};
return res
}
export const isApplicationType = (info: any) => {
return (
info?.type === TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_TO_ME
|| info?.type === TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_BY_ME
);
};
info?.type === TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_TO_ME ||
info?.type === TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_BY_ME
)
}
export const isFriend = (info: any): Promise<boolean> => {
return new Promise((resolve, reject) => {
if (info?.groupID || !info?.userID) {
resolve(false);
return;
resolve(false)
return
}
if (info?.addTime) {
resolve(true);
return;
resolve(true)
return
}
TUIFriendService.checkFriend({
userIDList: [info?.userID],
type: TUIChatEngine.TYPES.SNS_CHECK_TYPE_BOTH,
type: TUIChatEngine.TYPES.SNS_CHECK_TYPE_BOTH
})
.then((res: any) => {
switch (res?.data?.successUserIDList[0]?.relation) {
// No friend relationship: A does not have B in his friend list, and B does not have A in his friend list
case TUIChatEngine.TYPES.SNS_TYPE_NO_RELATION:
resolve(false);
break;
resolve(false)
break
// Single-item friend: A has B in his friend list, but B does not have A in his friend list
case TUIChatEngine.TYPES.SNS_TYPE_A_WITH_B:
resolve(false);
break;
resolve(false)
break
// Single-item friend: A does not have B in his friend list, but B has A in his friend list
case TUIChatEngine.TYPES.SNS_TYPE_B_WITH_A:
resolve(false);
break;
resolve(false)
break
// Two-way friendship
case TUIChatEngine.TYPES.SNS_TYPE_BOTH_WAY:
resolve(true);
break;
resolve(true)
break
default:
resolve(false);
break;
resolve(false)
break
}
})
.catch((error: any) => {
console.warn('checkFriend error', error);
reject(error);
});
});
};
console.warn('checkFriend error', error)
reject(error)
})
})
}
// Change friends remark
export const updateFriendRemark = (userID: string, remark: string) => {
// eslint-disable-next-line no-control-regex
if (remark?.replace(/[^\u0000-\u00ff]/g, 'aa')?.length > 96) {
Toast({
message: TUITranslateService.t('TUIContact.修改备注失败: 备注长度不得超过 96 字节'),
type: TOAST_TYPE.ERROR,
});
return;
message: TUITranslateService.t(
'TUIContact.修改备注失败: 备注长度不得超过 96 字节'
),
type: TOAST_TYPE.ERROR
})
return
}
TUIFriendService.updateFriend({
userID,
remark,
remark
})
.then(() => {
Toast({
message: TUITranslateService.t('TUIContact.修改备注成功'),
type: TOAST_TYPE.SUCCESS,
});
type: TOAST_TYPE.SUCCESS
})
})
.catch((error: any) => {
console.warn('update friend remark failed:', error);
console.warn('update friend remark failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.修改备注失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}
// Delete one friend
export const deleteFriend = (userID: string) => {
TUIFriendService.deleteFriend({
userIDList: [userID],
type: TUIChatEngine.TYPES.SNS_DELETE_TYPE_BOTH,
type: TUIChatEngine.TYPES.SNS_DELETE_TYPE_BOTH
})
.then((res: any) => {
const { successUserIDList } = res.data;
const { successUserIDList } = res.data
if (successUserIDList[0].userID === userID) {
Toast({
message: TUITranslateService.t('TUIContact.删除好友成功'),
type: TOAST_TYPE.SUCCESS,
});
type: TOAST_TYPE.SUCCESS
})
} else {
Toast({
message: TUITranslateService.t('TUIContact.删除好友失败'),
type: TOAST_TYPE.ERROR,
});
type: TOAST_TYPE.ERROR
})
}
})
.catch((error: any) => {
console.warn('delete friend failed:', error);
console.warn('delete friend failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.删除好友失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}
// Add friend
export const addFriend = (params: AddFriendParams) => {
@@ -178,54 +181,54 @@ export const addFriend = (params: AddFriendParams) => {
.then(() => {
Toast({
message: TUITranslateService.t('TUIContact.申请已发送'),
type: TOAST_TYPE.SUCCESS,
});
type: TOAST_TYPE.SUCCESS
})
})
.catch((error: any) => {
console.warn('delete friend failed:', error);
console.warn('delete friend failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.申请发送失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}
// Enter conversation
export const enterConversation = (item: any) => {
const conversationID = item?.groupID
? `GROUP${item?.groupID}`
: `C2C${item?.userID}`;
: `C2C${item?.userID}`
TUIConversationService.switchConversation(conversationID).catch(
(error: any) => {
console.warn('switch conversation failed:', error);
console.warn('switch conversation failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.进入会话失败'),
type: TOAST_TYPE.ERROR,
});
},
);
};
type: TOAST_TYPE.ERROR
})
}
)
}
// Accept friend application
export const acceptFriendApplication = (userID: string) => {
TUIFriendService.acceptFriendApplication({
userID,
type: TUIChatEngine.TYPES.SNS_APPLICATION_AGREE_AND_ADD,
type: TUIChatEngine.TYPES.SNS_APPLICATION_AGREE_AND_ADD
})
.then(() => {
Toast({
message: TUITranslateService.t('TUIContact.添加好友成功'),
type: TOAST_TYPE.SUCCESS,
});
type: TOAST_TYPE.SUCCESS
})
})
.catch((error: any) => {
console.warn('accept friend application failed:', error);
console.warn('accept friend application failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.同意好友申请失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}
// Refuse friend application
export const refuseFriendApplication = (userID: string) => {
@@ -233,127 +236,164 @@ export const refuseFriendApplication = (userID: string) => {
.then(() => {
Toast({
message: TUITranslateService.t('TUIContact.拒绝成功'),
type: TOAST_TYPE.SUCCESS,
});
type: TOAST_TYPE.SUCCESS
})
})
.catch((error: any) => {
console.warn('accept friend application failed:', error);
console.warn('accept friend application failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.拒绝好友申请失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}
// Dismiss group
export const dismissGroup = (groupID: string) => {
TUIGroupService.dismissGroup(groupID)
deleteImGroup(groupID)
.then(() => {
Toast({
message: TUITranslateService.t('TUIContact.解散群聊成功'),
type: TOAST_TYPE.SUCCESS,
});
TUIGlobal?.updateContactSearch && TUIGlobal?.updateContactSearch();
type: TOAST_TYPE.SUCCESS
})
TUIGlobal?.updateContactSearch && TUIGlobal?.updateContactSearch()
switchTab('/TUIKit/components/TUIConversation/index')
})
.catch((error: any) => {
console.warn('dismiss group failed:', error);
console.warn('dismiss group failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.解散群聊失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
// TUIGroupService.dismissGroup(groupID)
// .then(() => {
// Toast({
// message: TUITranslateService.t('TUIContact.解散群聊成功'),
// type: TOAST_TYPE.SUCCESS,
// });
// TUIGlobal?.updateContactSearch && TUIGlobal?.updateContactSearch();
// })
// .catch((error: any) => {
// console.warn('dismiss group failed:', error);
// Toast({
// message: TUITranslateService.t('TUIContact.解散群聊失败'),
// type: TOAST_TYPE.ERROR,
// });
// });
}
// Quit group
export const quitGroup = (groupID: string) => {
TUIGroupService.quitGroup(groupID)
console.log('222')
quitImGroup(groupID)
.then(() => {
Toast({
message: TUITranslateService.t('TUIContact.退出群组成功'),
type: TOAST_TYPE.SUCCESS,
});
type: TOAST_TYPE.SUCCESS
})
switchTab('/TUIKit/components/TUIConversation/index')
})
.catch((error: any) => {
console.warn('quit group failed:', error);
console.warn('quit group failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.退出群组失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
// TUIGroupService.quitGroup(groupID)
// .then(() => {
// Toast({
// message: TUITranslateService.t('TUIContact.退出群组成功'),
// type: TOAST_TYPE.SUCCESS,
// });
// })
// .catch((error: any) => {
// console.warn('quit group failed:', error);
// Toast({
// message: TUITranslateService.t('TUIContact.退出群组失败'),
// type: TOAST_TYPE.ERROR,
// });
// });
}
// Join group
export const joinGroup = (groupID: string, applyMessage?: string) => {
TUIGroupService.joinGroup({
groupID,
applyMessage,
applyMessage
} as JoinGroupParams)
.then((imResponse: { data: { status?: string } }) => {
switch (imResponse?.data?.status) {
case TUIChatEngine.TYPES.JOIN_STATUS_WAIT_APPROVAL: // Wait for administrator approval
Toast({
message: TUITranslateService.t('TUIContact.等待管理员同意'),
type: TOAST_TYPE.SUCCESS,
});
break;
type: TOAST_TYPE.SUCCESS
})
break
case TUIChatEngine.TYPES.JOIN_STATUS_SUCCESS: // Join group successfully
Toast({
message: TUITranslateService.t('TUIContact.加群成功'),
type: TOAST_TYPE.SUCCESS,
});
break;
type: TOAST_TYPE.SUCCESS
})
break
case TUIChatEngine.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // Already in the group
Toast({
message: TUITranslateService.t('TUIContact.您已是群成员'),
type: TOAST_TYPE.SUCCESS,
});
break;
type: TOAST_TYPE.SUCCESS
})
break
default:
break;
break
}
})
.catch((error: any) => {
console.warn('join group failed:', error);
console.warn('join group failed:', error)
Toast({
message: '申请入群失败',
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}
// Add to blacklist
export const addToBlacklist = (userID: string, successCallBack?: () => void) => {
export const addToBlacklist = (
userID: string,
successCallBack?: () => void
) => {
TUIUserService.addToBlacklist({
userIDList: [userID],
userIDList: [userID]
})
.then(() => {
successCallBack && successCallBack();
successCallBack && successCallBack()
})
.catch((error: any) => {
console.warn('add to blacklist failed:', error);
console.warn('add to blacklist failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.加入黑名单失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}
// Remove from Blacklist
export const removeFromBlacklist = (
userID: string,
successCallBack?: () => void,
successCallBack?: () => void
) => {
TUIUserService.removeFromBlacklist({
userIDList: [userID],
userIDList: [userID]
})
.then(() => {
successCallBack && successCallBack();
successCallBack && successCallBack()
})
.catch((error: any) => {
console.warn('remove from blacklist failed:', error);
console.warn('remove from blacklist failed:', error)
Toast({
message: TUITranslateService.t('TUIContact.移除黑名单失败'),
type: TOAST_TYPE.ERROR,
});
});
};
type: TOAST_TYPE.ERROR
})
})
}

View File

@@ -6,7 +6,7 @@
<label class="group-list-item-label">
{{ TUITranslateService.t('TUIGroup.群头像') }}
</label>
<Avatar :url="groupInfo.profile.avatar" />
<Avatar :url="groupAvatar" @click="selectAvatar" />
</li>
<ul>
<li
@@ -158,21 +158,12 @@
import Server from '../server'
import { IUserProfile } from '../../../interface'
import { createImGroup } from '../../../../api/tui-kit'
import { chooseImage } from '../../../../utils/media'
import { uploadSingleFile } from '../../../../utils/uploadFile'
const TUIGroupServer = Server.getInstance()
const TUIConstants = TUIGroupServer.constants
/** 自定义数据 */
const cbPopupShow = ref(null)
const applyJoinOption = ref('NeedPermission')
const applyJoinOptionName = computed(() => {
const data = groupJoinTypeConfig.find(
v => v.value === applyJoinOption.value
)
return data?.label || ''
})
// =======
const groupInfo = reactive<any>({
profile: {
groupID: '',
@@ -195,6 +186,19 @@
isEdit: false
})
/** 自定义数据 */
const cbPopupShow = ref(null)
const applyJoinOption = ref('NeedPermission')
// 群头像
const groupAvatar = ref(groupInfo.profile.avatar)
const applyJoinOptionName = computed(() => {
const data = groupJoinTypeConfig.find(
v => v.value === applyJoinOption.value
)
return data?.label || ''
})
// =======
watchEffect(() => {
const params = TUIGroupServer.getOnCallParams(
TUIConstants.TUIGroup.SERVICE.METHOD.CREATE_GROUP
@@ -247,35 +251,69 @@
}
}
/** 选择群头像 */
const selectAvatar = async () => {
const paths = await chooseImage({ count: 1 })
groupAvatar.value = await uploadSingleFile(paths[0], {
url: '/api/common/admin/upload/up/single'
})
}
const createGroup = async (options: any) => {
console.log('确认创建==', options)
// const data = {
// currentMemberCount:
// }
// createImGroup()
return
try {
options.memberList = options.memberList.map(
(item: IUserProfile) => {
return { userID: item.userID }
}
)
if (options.type === TUIChatEngine.TYPES.GRP_COMMUNITY) {
delete options.groupID
}
const res = await TUIGroupService.createGroup(options)
const { type } = res.data.group
if (type === TUIChatEngine.TYPES.GRP_AVCHATROOM) {
await TUIGroupService.joinGroup({
groupID: res.data.group.groupID,
applyMessage: ''
const data = {
applyJoinOption: applyJoinOption.value,
faceUrl: groupAvatar.value,
groupName: options.name,
groupType: options.type,
memberList: options.memberList.map((item: IUserProfile) => {
return { memberAccount: item.userID }
})
}
handleCompleteCreate(res.data.group)
const res = await createImGroup(data)
const e = res.data
// const type = e.groupType
// if (type === TUIChatEngine.TYPES.GRP_AVCHATROOM) {
// await TUIGroupService.joinGroup({
// groupID: e.groupID,
// applyMessage: ''
// })
// }
const newRes = await TUIGroupService.getGroupProfile({
groupID: e.groupId
})
handleCompleteCreate(newRes.data.group)
Toast({
message: TUITranslateService.t('TUIGroup.群组创建成功'),
type: TOAST_TYPE.SUCCESS
})
// ========= 原本逻辑
// options.memberList = options.memberList.map(
// (item: IUserProfile) => {
// return { userID: item.userID }
// }
// )
// if (options.type === TUIChatEngine.TYPES.GRP_COMMUNITY) {
// delete options.groupID
// }
// const res = await TUIGroupService.createGroup(options)
// console.log(res)
// const { type } = res.data.group
// if (type === TUIChatEngine.TYPES.GRP_AVCHATROOM) {
// await TUIGroupService.joinGroup({
// groupID: res.data.group.groupID,
// applyMessage: ''
// })
// }
// handleCompleteCreate(res.data.group)
// Toast({
// message: TUITranslateService.t('TUIGroup.群组创建成功'),
// type: TOAST_TYPE.SUCCESS
// })
} catch (err: any) {
Toast({
message: err.message,

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,10 @@
<div class="group-info" @click="toggleEditStatus">
<Avatar
useSkeletonAnimation
:url="groupProfile.avatar ||
'https://web.sdk.qcloud.com/im/demo/TUIkit/web/img/constomer.svg'"
:url="
groupProfile.avatar ||
'https://web.sdk.qcloud.com/im/demo/TUIkit/web/img/constomer.svg'
"
size="40px"
/>
<div class="group-details">
@@ -16,28 +18,26 @@
<div
v-if="isEdit"
:class="{
'edit-h5': isMobile,
'edit-h5': isMobile
}"
>
<main class="edit-h5-main">
<header
v-if="!isPC"
class="edit-h5-header"
>
<header v-if="!isPC" class="edit-h5-header">
<aside class="left">
<h1 class="title">{{ TUITranslateService.t(`TUIGroup.修改群聊名称`) }}</h1>
<span class="subtitle">{{
TUITranslateService.t(
`TUIGroup.修改群聊名称后,将在群内通知其他成员`
)
}}</span>
<h1 class="title">
{{ TUITranslateService.t(`TUIGroup.修改群聊名称`) }}
</h1>
<span class="subtitle">
{{
TUITranslateService.t(
`TUIGroup.修改群聊名称后,将在群内通知其他成员`
)
}}
</span>
</aside>
<span
class="close"
@click="toggleEditStatus"
>{{
TUITranslateService.t(`关闭`)
}}</span>
<span class="close" @click="toggleEditStatus">
{{ TUITranslateService.t(`关闭`) }}
</span>
</header>
<div class="input-box">
<input
@@ -46,24 +46,17 @@
v-model="inputGroupName"
class="input"
type="text"
>
<span
v-if="!isPC"
class="tip"
>{{
TUITranslateService.t(
`TUIGroup.仅限中文、字母、数字和下划线2-20个字`
)
}}</span>
/>
<span v-if="!isPC" class="tip">
{{
TUITranslateService.t(
`TUIGroup.仅限中文字母数字和下划线2-20个字`
)
}}
</span>
</div>
<footer
v-if="!isPC"
class="edit-h5-footer"
>
<button
class="btn"
@click="updateProfile"
>
<footer v-if="!isPC" class="edit-h5-footer">
<button class="btn" @click="updateProfile">
{{ TUITranslateService.t(`确认`) }}
</button>
</footer>
@@ -73,226 +66,226 @@
</template>
<script lang="ts" setup>
import { watchEffect, ref, nextTick, watch } from '../../../adapter-vue';
import {
TUITranslateService,
IGroupModel,
} from '@tencentcloud/chat-uikit-engine-lite';
import Avatar from "../../common/Avatar/index.vue";
import Icon from '../../common/Icon.vue';
import rightIcon from '../../../assets/icon/right-icon.svg';
import { Toast, TOAST_TYPE } from '../../common/Toast/index';
import { isMobile, isPC } from '../../../utils/env';
import { watchEffect, ref, nextTick, watch } from '../../../adapter-vue'
import {
TUITranslateService,
IGroupModel
} from '@tencentcloud/chat-uikit-engine-lite'
import Avatar from '../../common/Avatar/index.vue'
import Icon from '../../common/Icon.vue'
import rightIcon from '../../../assets/icon/right-icon.svg'
import { Toast, TOAST_TYPE } from '../../common/Toast/index'
import { isMobile, isPC } from '../../../utils/env'
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
isAuthor: {
type: Boolean,
default: false,
},
});
const props = defineProps({
data: {
type: Object,
default: () => ({})
},
isAuthor: {
type: Boolean,
default: false
}
})
const groupProfile = ref<IGroupModel>({});
const inputGroupName = ref('');
const isEdit = ref(false);
const nameInputRef = ref<HTMLInputElement>(null);
const groupProfile = ref<IGroupModel>({})
const inputGroupName = ref('')
const isEdit = ref(false)
const nameInputRef = ref<HTMLInputElement>(null)
watchEffect(() => {
groupProfile.value = props.data;
});
watchEffect(() => {
groupProfile.value = props.data
})
const emit = defineEmits(['update']);
const updateProfile = () => {
if (!inputGroupName.value) {
Toast({
message: TUITranslateService.t('TUIGroup.群名称不能为空'),
type: TOAST_TYPE.ERROR,
});
} else {
if (inputGroupName.value !== groupProfile.value.name) {
emit('update', { key: 'name', value: inputGroupName.value });
groupProfile.value.name = inputGroupName.value;
inputGroupName.value = '';
const emit = defineEmits(['update'])
const updateProfile = () => {
if (!inputGroupName.value) {
Toast({
message: TUITranslateService.t('TUIGroup.群名称修改成功'),
type: TOAST_TYPE.SUCCESS,
});
message: TUITranslateService.t('TUIGroup.群名称不能为空'),
type: TOAST_TYPE.ERROR
})
} else {
if (inputGroupName.value !== groupProfile.value.name) {
emit('update', { key: 'name', value: inputGroupName.value })
groupProfile.value.name = inputGroupName.value
inputGroupName.value = ''
Toast({
message: TUITranslateService.t('TUIGroup.群名称修改成功'),
type: TOAST_TYPE.SUCCESS
})
}
toggleEditStatus()
}
toggleEditStatus();
}
};
const toggleEditStatus = () => {
if (props.isAuthor) {
isEdit.value = !isEdit.value;
}
if (isEdit.value) {
inputGroupName.value = groupProfile.value.name;
}
};
watch(
() => isEdit.value,
(newVal: boolean) => {
if (newVal) {
nextTick(() => {
nameInputRef.value?.focus();
});
const toggleEditStatus = () => {
if (props.isAuthor) {
isEdit.value = !isEdit.value
}
},
);
if (isEdit.value) {
inputGroupName.value = groupProfile.value.name
}
}
watch(
() => isEdit.value,
(newVal: boolean) => {
if (newVal) {
nextTick().then(() => {
nameInputRef.value?.focus()
})
}
}
)
</script>
<style lang="scss" scoped>
@import "../../../assets/styles/common";
@import '../../../assets/styles/common';
.group-name {
padding: 14px 20px;
font-weight: 400;
font-size: 14px;
color: #000;
display: flex;
flex-direction: column;
}
.group-info {
display: flex;
gap: 10px;
align-items: center;
}
.group-details {
min-width: 0;
flex: 1;
display: flex;
gap: 6px;
flex-direction: column;
.name {
font-size: 16px;
line-height: 18px;
font-weight: 500;
display: flex;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.ID {
font-size: 12px;
line-height: 14px;
font-weight: 400;
color: #888;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
}
.input-box {
display: flex;
.input {
flex: 1;
border: 1px solid #e8e8e9;
border-radius: 4px;
padding: 4px 16px;
.group-name {
padding: 14px 20px;
font-weight: 400;
font-size: 14px;
color: #000;
opacity: 0.6;
}
}
.space-top {
border-top: 10px solid #f4f5f9;
}
.edit-h5 {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1;
.edit-h5-main {
background: #fff;
flex: 1;
padding: 18px;
border-radius: 12px 12px 0 0;
width: 80vw;
.input-box {
flex-direction: column;
padding: 18px 0;
.input {
background: #f8f8f8;
padding: 10px 12px;
}
.tip {
font-size: 12px;
color: #888;
padding-top: 8px;
}
}
}
&-header {
display: flex;
flex-direction: column;
}
.group-info {
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
}
h1 {
font-family: PingFang SC;
.group-details {
min-width: 0;
flex: 1;
display: flex;
gap: 6px;
flex-direction: column;
.name {
font-size: 16px;
line-height: 18px;
font-weight: 500;
font-size: 22px;
line-height: 26px;
display: flex;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.subtitle {
color: #888;
}
.close {
font-family: PingFangSC-Regular;
.ID {
font-size: 12px;
line-height: 14px;
font-weight: 400;
font-size: 18px;
color: #3370ff;
letter-spacing: 0;
line-height: 27px;
color: #888;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
}
&-footer {
.input-box {
display: flex;
.btn {
.input {
flex: 1;
border: none;
background: #147aff;
border-radius: 5px;
font-family: PingFangSC-Regular;
border: 1px solid #e8e8e9;
border-radius: 4px;
padding: 4px 16px;
font-weight: 400;
font-size: 16px;
color: #fff;
letter-spacing: 0;
line-height: 27px;
padding: 8px 0;
font-size: 14px;
color: #000;
opacity: 0.6;
}
}
&:disabled {
opacity: 0.3;
.space-top {
border-top: 10px solid #f4f5f9;
}
.edit-h5 {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1;
.edit-h5-main {
background: #fff;
flex: 1;
padding: 18px;
border-radius: 12px 12px 0 0;
width: 80vw;
.input-box {
flex-direction: column;
padding: 18px 0;
.input {
background: #f8f8f8;
padding: 10px 12px;
}
.tip {
font-size: 12px;
color: #888;
padding-top: 8px;
}
}
}
&-header {
display: flex;
align-items: center;
justify-content: space-between;
h1 {
font-family: PingFang SC;
font-weight: 500;
font-size: 22px;
line-height: 26px;
}
.subtitle {
color: #888;
}
.close {
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 18px;
color: #3370ff;
letter-spacing: 0;
line-height: 27px;
}
}
&-footer {
display: flex;
.btn {
flex: 1;
border: none;
background: #147aff;
border-radius: 5px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 16px;
color: #fff;
letter-spacing: 0;
line-height: 27px;
padding: 8px 0;
&:disabled {
opacity: 0.3;
}
}
}
}
}
</style>