即时通信 IM uni-app(添加自定义消息)

TUIKit 默认实现了文本、图片、语音、视频、文件等基本消息类型的发送和展示,如果这些类型满足不了你的需求,可以以新增自定义消息类型(订单卡片、商品卡片、问卷、骰子、链接卡等)。

相关产品
即时通信 IM 产品页,含版本规格、价格与购买入口。

一、基本消息类型

消息类型 显示效果图
文本类消息 
图片类消息 
语音类消息 
视频类消息 
文件类消息 

二、步骤 1:定义业务数据

自定义消息需要传入哪些数据是用户自行决定的,假设我们要开发一个订单卡片(OrderCard)的自定义消息,示例代码如下所示:

{ businessID: 'order_card', // 业务类型标识,渲染分发依赖此字段 version: 1, // 协议版本号,后续字段升级时用于兼容判断 orderID: 'TX20260602001', title: '腾讯云 IM 套餐 · 旗舰版', price: 2999, cover: 'https://web.sdk.qcloud.com/im/assets/images/tencent_rtc_logo.png' // 演示图片,业务方请替换为自己的图片}

三、步骤 2:创建自定义消息组件

新建 OrderCardMessage.nvue,该组件接收 message 属性,解析 message.messageBody.customMessage.data 进行消息渲染。

<template> <view v-if="data.businessID === 'order_card'" class="order-card"> <view class="order-card__cover"> <image class="order-card__cover-image" :src="data.cover" mode="aspectFit" resize="contain" /> </view> <view class="order-card__body"> <text class="order-card__title">{{ data.title }}</text> <text class="order-card__id">订单号:{{ data.orderID }}</text> <text class="order-card__price">¥{{ priceText }}</text> </view> </view></template><script setup>import { computed } from 'vue';const props = defineProps({ message: { type: Object, required: true }})const data = computed(() => { try { return JSON.parse(props.message.messageBody?.customMessage?.data || '{}') } catch (e) { return {} }})const priceText = computed(() => Number(data.value.price || 0).toFixed(2))</script><style> .order-card { width: 480rpx; background-color: #FFFFFF; border-radius: 16rpx; overflow: hidden; } .order-card__cover { width: 480rpx; height: 240rpx; justify-content: center; align-items: center; } .order-card__cover-image { width: 480rpx; height: 240rpx; } .order-card__body { padding: 20rpx; } .order-card__title { font-size: 30rpx; color: #1A1A1A; lines: 2; } .order-card__id { font-size: 24rpx; color: #999; margin-top: 8rpx; } .order-card__price { font-size: 32rpx; color: #FF4D4F; font-weight: 600; margin-top: 16rpx; }</style>

四、步骤 3:注册到 MessageList

MessageList 的 customMessageRender 接受一个 (message, conversationID) => Component | null 工厂函数,根据 步骤 1:定义业务数据 中约定的 key(businessID)返回对应业务组件即可,返回 null 自动降级为内置渲染。

注意:说明:

注意:customMessageRender 对自己发出去的、对方发过来的、滚屏加载的历史消息全部生效。如果接收端没注册渲染器,会自动降级为 TUIKit 内置 CustomMessage 默认渲染。

<template> <MessageList :conversationID="conversationID" :customMessageRender="renderCustomMessage" /></template><script setup>import { ref } from 'vue';import { onLoad } from '@dcloudio/uni-app';import MessageList from '@/uni_modules/tuikit-atomic-x/components/MessageList/MessageList.nvue';import OrderCardMessage from './OrderCardMessage.nvue'const renderCustomMessage = (message) => { try { const data = JSON.parse(message.messageBody?.customMessage?.data || '{}') if (data.businessID === 'order_card') return OrderCardMessage } catch (e) {} return null}const conversationID = ref('');// ==================== 生命周期 ====================onLoad((options) => { if (!options.conversationID) { console.error('未接收到 conversationID 参数') return } conversationID.value = options.conversationID;})</script>

五、步骤 4:触发发送自定义消息

在 MessageInput 的 toolList 中追加一个发送订单的工具按钮,在 callback 里调用 sendCustomMessage 发送订单卡片消息。

注意:说明:

<script setup>import { ref, shallowRef } from 'vue';import { onLoad } from '@dcloudio/uni-app';import { DEFAULT_TOOLS } from '@/uni_modules/tuikit-atomic-x/components/MessageInput/MessageInput.nvue'import { useMessageInputState } from '@/uni_modules/tuikit-atomic-x/state/MessageInputState'const conversationID = ref('');const inputState = shallowRef(null)// 在内置工具基础上追加一个"订单"按钮const toolList = [ …DEFAULT_TOOLS, { id: 'order', name: '订单', icon: '/uni_modules/tuikit-atomic-x/static/icon/custom.png', // 演示用工程内既有图标,业务方请替换为自己的图标 callback: () => { // 实际业务一般跳订单选择页,回来后用拿到的订单数据调发送 inputState.value?.sendCustomMessage({ businessID: 'order_card', version: 1, orderID: 'TX20260602001', title: '腾讯云 IM 套餐 · 旗舰版', price: 2999, cover: 'https://web.sdk.qcloud.com/im/assets/images/tencent_rtc_logo.png' // 演示图片,业务方请替换为自己的图片 }).catch(err => uni.showToast({ icon: 'none', title: err.message || '发送失败' })) } }]onLoad((options) => { if (!options.conversationID) { console.error('未接收到 conversationID 参数') return } conversationID.value = options.conversationID; inputState.value = useMessageInputState({ conversationID: conversationID.value });})</script><template> <MessageInput :conversationID="conversationID" :toolList="toolList" /></template>

六、完整示例代码

修改聊天页 pages/scenes/chat/chat/index.nvue,新增订单按钮 + 订单卡片消息渲染。下面是改完后的完整代码(复制覆盖即可),同目录下新增一个 OrderCardMessage.nvue 组件。

pages/scenes/chat/chat/index.nvuepages/scenes/chat/chat/OrderCardMessage.nvue<template> <view class="page-container"> <CustomNavbar position="fixed" :title="navigationTitle" :showBack="true" :showMenu="true" @back="onNavBack" @menuSelect="onNavMenuSelect" /> <view class="message-list-container" :style="{ paddingBottom: inputToolbarHeight + 'px' }" @tap="closeSoftKeyboard" > <MessageList v-if="conversationID" :conversationID="conversationID" :locateMessage="locateMessage" :inputToolbarHeight="inputToolbarHeight" :inputPanelHeight="inputPanelHeight" :customMessageRender="renderCustomMessage" @onMessageListTap="closeSoftKeyboard" /> </view> <MessageInput v-if="conversationID" ref="messageInputRef" :conversationID="conversationID" :toolList="toolList" :setOfflinePushInfo="setOfflinePushInfo" @height-change="onMessageInputHeightChange" /> </view></template><script setup lang="ts">import { ref, shallowRef, computed } from 'vue'import { onLoad, onShow } from '@dcloudio/uni-app'import MessageList from '@/uni_modules/tuikit-atomic-x/components/MessageList/MessageList.nvue'import MessageInput, { DEFAULT_TOOLS } from '@/uni_modules/tuikit-atomic-x/components/MessageInput/MessageInput.nvue'import CustomNavbar from '@/uni_modules/tuikit-atomic-x/components/CustomNavbar/CustomNavbar.nvue'import { useConversationListState } from '@/uni_modules/tuikit-atomic-x/state/ConversationListState'import { useGroupState } from '@/uni_modules/tuikit-atomic-x/state/GroupState'import { useMessageInputState } from '@/uni_modules/tuikit-atomic-x/state/MessageInputState'import { MessageType } from '@/uni_modules/tuikit-atomic-x/types/message'import type { OfflinePushContext, OfflinePushInfo } from '@/uni_modules/tuikit-atomic-x/types/message'import OrderCardMessage from './OrderCardMessage.nvue'const { joinedGroupList, fetchJoinedGroupList } = useGroupState()const conversationID = ref('')const locateMessage = ref(null)const isInGroup = ref(false)const messageInputRef = ref(null)const inputToolbarHeight = ref(0)const inputPanelHeight = ref(0)const inputState = shallowRef<ReturnType<typeof useMessageInputState> | null>(null)const { fetchConversationInfo, conversationList } = useConversationListState('chat')const isC2CConversation = computed(() => conversationID.value.startsWith('c2c_'))const currentConversation = computed(() => conversationList.value.find(conv => conv.conversationID === conversationID.value))const navigationTitle = computed(() => currentConversation.value?.title || '聊天')// ==================== 自定义消息:订单卡片 ====================const toolList = [ …DEFAULT_TOOLS, { id: 'order', name: '订单', icon: '/uni_modules/tuikit-atomic-x/static/icon/custom.png', // 演示用工程内既有图标,业务方请替换为自己的图标 callback: () => { // 实际业务一般跳订单选择页,回来后用拿到的订单数据调发送 inputState.value?.sendCustomMessage({ businessID: 'order_card', version: 1, orderID: 'TX20260602001', title: '腾讯云 IM 套餐 · 旗舰版', price: 2999, cover: 'https://web.sdk.qcloud.com/im/assets/images/tencent_rtc_logo.png' // 演示图片,业务方请替换为自己的图片 }).catch(err => uni.showToast({ icon: 'none', title: err.message || '发送失败' })) } }]const renderCustomMessage = (message: any) => { try { const data = JSON.parse(message.messageBody?.customMessage?.data || '{}') if (data.businessID === 'order_card') return OrderCardMessage } catch (e) {} return null}// ==================== 导航栏 ====================const onNavBack = () => uni.navigateBack()const onNavMenuSelect = () => { if (!isC2CConversation.value) { isInGroup.value = joinedGroupList.value.some( group => group.groupID === conversationID.value?.replace('group_', '') ) if (!isInGroup.value) { uni.showToast({ title: '你已不在群内,无法进行此操作', icon: 'none' }) return } } uni.navigateTo({ url: `/pages/scenes/chat/chatSetting/index?conversationID=${conversationID.value}&showType=0` })}// ==================== 输入框 ====================const onMessageInputHeightChange = ({ inputToolbarHeight: toolbar, inputPanelHeight: panel }) => { inputToolbarHeight.value = toolbar inputPanelHeight.value = panel}const closeSoftKeyboard = () => messageInputRef.value?.collapse()// ==================== 离线推送 ====================const setOfflinePushInfo = (ctx: OfflinePushContext): OfflinePushInfo | null => { const title = currentConversation.value?.title || '新消息' let description = '[新消息]' switch (ctx.messageType) { case MessageType.TEXT: description = (ctx.messageBody as { text?: string })?.text || '[文本消息]' break case MessageType.IMAGE: description = '[图片]'; break case MessageType.VIDEO: description = '[视频]'; break case MessageType.SOUND: description = '[语音]'; break case MessageType.FILE: description = '[文件]'; break case MessageType.CUSTOM: return null default: return null } return { title, description, extensionInfo: { ext: JSON.stringify({ conversationID: ctx.conversationID, messageType: ctx.messageType }) } }}// ==================== 生命周期 ====================onLoad((options) => { if (!options.conversationID) { console.error('未接收到 conversationID 参数') return } conversationID.value = options.conversationID; inputState.value = useMessageInputState({ conversationID: conversationID.value }); fetchConversationInfo(conversationID.value) fetchJoinedGroupList() if (uni.$locateMessage) { locateMessage.value = uni.$locateMessage uni.$locateMessage = null }})onShow(() => fetchConversationInfo(conversationID.value))</script><style>.page-container { flex: 1; flex-direction: column; background-color: #F9FAFC; }.message-list-container { flex: 1; flex-direction: column; background-color: #F9FAFC; }</style><template> <view v-if="data.businessID === 'order_card'" class="order-card"> <view class="order-card__cover"> <image class="order-card__cover-image" :src="data.cover" mode="aspectFit" resize="contain" /> </view> <view class="order-card__body"> <text class="order-card__title">{{ data.title }}</text> <text class="order-card__id">订单号:{{ data.orderID }}</text> <text class="order-card__price">¥{{ priceText }}</text> </view> </view></template><script setup>import { computed } from 'vue'const props = defineProps({ message: { type: Object, required: true }})const data = computed(() => { try { return JSON.parse(props.message.messageBody?.customMessage?.data || '{}') } catch (e) { return {} }})const priceText = computed(() => Number(data.value.price || 0).toFixed(2))</script><style> .order-card { width: 480rpx; background-color: #FFFFFF; border-radius: 16rpx; overflow: hidden; } .order-card__cover { width: 480rpx; height: 240rpx; justify-content: center; align-items: center; } .order-card__cover-image { width: 480rpx; height: 240rpx; } .order-card__body { padding: 20rpx; } .order-card__title { font-size: 30rpx; color: #1A1A1A; lines: 2; } .order-card__id { font-size: 24rpx; color: #999; margin-top: 8rpx; } .order-card__price { font-size: 32rpx; color: #FF4D4F; font-weight: 600; margin-top: 16rpx; }</style>

效果如下:

赞 (0)
上一篇 4天前
下一篇 4天前