添加红包功能

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,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>