Frontend Development 33 min read

Tencent IM Overview and Component Design for Instant Messaging Applications

This article provides a comprehensive technical guide on Tencent Cloud's instant messaging (IM) service, comparing UI‑integrated and non‑UI integration approaches, detailing the core chat and input components, their Vue/TypeScript implementations, rendering logic, event handling, and auxiliary features such as file upload simulation, scroll management, and voice/video calling.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Tencent IM Overview and Component Design for Instant Messaging Applications

Tencent IM Overview

Tencent is one of the earliest and largest instant‑messaging providers in China, offering QQ and WeChat. To support digital transformation, Tencent Cloud exposes high‑concurrency, high‑reliability IM capabilities via SDKs and REST APIs, allowing developers to embed chat functionality into their applications.

Access Methods

Tencent IM offers two integration modes:

UI‑integrated solution (quick to integrate, full feature set, but UI cannot be customized and component code may clash with existing projects).

Non‑UI solution (API‑only, lightweight code that matches project style, but developers must design the UI and implement basic chat logic).

IM Framework Design

The core of the chat UI consists of two components: the chat content area and the message input area.

<template>
<div>
<ChatContent />
<ChatFooter />
</div>
</template>

Component List

Chat box components include MessageLoadMore, MessageItem, MessageTimestamp, MessageTip, MessageBubble, MessageTool, MessageText, ProgressMessage, MessageImage, MessageFile, MessageVideo, MessageRevoked, and ScrollButton.

Message input components include MessageInputToolbar, EmojiPicker, ImageUpload, FileUpload, VideoUpload, VoiceCall, VideoCall, and MessageInputEditor.

Message Rendering Logic

The message list is fetched via getMessageList , which returns messageList , nextReqMessageID , and isCompleted . When isCompleted is false, a "Load more" button is displayed.

export interface IMResponseData {
/** 消息列表 */
messageList: any[]
/** 用于续拉,分页续拉时需传入该字段 */
nextReqMessageID: string
/** 表示是否已经拉完所有消息 */
isCompleted: boolean
}

Scrolling to a specific message after loading older messages is handled by scrollToPosition which uses element.scrollIntoView .

const scrollToPosition = async (config: ScrollConfig = {}): Promise<void> => {
return new Promise((resolve, reject) => {
requestAnimationFrame(() => {
const targetMessageDom = document.querySelector(`#tui-${config.scrollToMessage}`)
if (targetMessageDom?.scrollIntoView) {
targetMessageDom.scrollIntoView({ behavior: 'smooth' })
}
resolve()
})
})
}

Timestamp Formatting

Messages are grouped by time; the calculateTimestamp function formats timestamps as "hh:mm", "昨天 hh:mm", weekday, month/day, or year/month/day depending on recency.

function calculateTimestamp(timestamp: number): string {
const todayZero = new Date().setHours(0, 0, 0, 0)
const thisYear = new Date(new Date().getFullYear(), 0, 1).getTime()
const target = new Date(timestamp)
const oneDay = 24 * 60 * 60 * 1000
const oneWeek = 7 * oneDay
const diff = todayZero - target.getTime()
function formatNum(num: number): string { return num < 10 ? '0' + num : num.toString() }
if (diff <= 0) { return `${formatNum(target.getHours())}:${formatNum(target.getMinutes())}` }
else if (diff <= oneDay) { return `昨天 ${formatNum(target.getHours())}:${formatNum(target.getMinutes())}` }
else if (diff <= oneWeek - oneDay) {
const weekdays = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']
const weekday = weekdays[target.getDay()]
return `${weekday} ${formatNum(target.getHours())}:${formatNum(target.getMinutes())}`
} else if (target.getTime() >= thisYear) {
return `${target.getMonth() + 1}/${target.getDate()} ${formatNum(target.getHours())}:${formatNum(target.getMinutes())}`
} else {
return `${target.getFullYear()}/${target.getMonth() + 1}/${target.getDate()} ${formatNum(target.getHours())}:${formatNum(target.getMinutes())}`
}
}

System Tips, Bubbles, and Risk Content

System messages are rendered with MessageTip . Message bubbles are aligned left or right based on flow . Risky content (e.g., unsafe images) is replaced with a placeholder using the hasRiskContent flag.

<img v-if="item.type === TYPES.MSG_IMAGE && item.hasRiskContent" :src="riskImageReplaceUrl" alt="图片无法查看" />
<div v-else>{{ riskContentText }}</div>

Loading Indicators and Failure Handling

When status === 'unSend' , a loading icon is shown; when status === 'fail' , a tooltip with a retry button appears.

<Icon v-if="item.status === 'unSend' && needLoadingIconMessageType.includes(item.type)" icon="eos-icons:three-dots-loading" :color="item.flow === 'in' ? '#38bdf8' : '#d4d4d8'" size="24" />
<Tooltip v-if="item.status === 'fail' || item.hasRiskContent" @click="resendMessage">
<template #title>发送失败</template>!</Tooltip>

Right‑Click Menu

Message actions such as revoke, delete, and copy are provided via a Dropdown triggered on the context menu.

<Dropdown :dropMenuList="MessageDropMenuList" :trigger="['contextmenu']" placement="bottom" overlayClassName="message__dropdown" @menu-event="handleMenuEvent">
<slot></slot>
</Dropdown>

Text Rendering and Emoji Parsing

Plain text is rendered safely without v-html to avoid XSS. Emoji shortcuts like [调皮] are parsed and replaced with image URLs.

const text = computed(() => {
const brackets = parseBrackets(payload.value.text || '')
return brackets.map(item => {
if (item === '\n') { return { name: 'br' } }
else if (item.startsWith('[') && item.endsWith(']') && basicEmojiMap[item]) {
return { name: 'img', src: basicEmojiUrl + basicEmojiMap[item] }
} else { return { name: 'text', text: item } }
})
})

File Upload Simulation

A simple upload‑progress simulator assumes a network speed of 512 KB/s and updates progress up to 99 %.

function simulateFileUpload(fileSize: number, callback: { (p: any): void }) {
const totalUploadTime = fileSize / (1024 * 512)
const totalIntervals = totalUploadTime * 10
let currentInterval = 0
timer = setInterval(() => {
currentInterval++
const progress = (currentInterval / totalIntervals) * 100
callback(parseInt(Math.min(progress, 99)))
if (currentInterval >= totalIntervals) { clearInterval(timer) }
}, 100)
}

Image Component

Image payloads contain multiple resolutions; the component selects a compressed version for display and the original for preview, while also providing explicit height and width to avoid layout shifts.

export interface ImagePayload { uuid: string; imageFormat: 1|2|3|4|255; imageInfoArray: ImageInfo[] }
export interface ImageInfo { type: 0|1|2; width: number; height: number; size: number; url: string }

File and Video Components

File messages show name and size with a download click; video messages use the video tag with a poster image.

<div title="单击下载" @click="download">
<Icon icon="ant-design:file-twotone" :size="40" />
<div>{{ item.payload.fileName }}</div>
<div>{{ fileSize }}</div>
</div>

Scroll Button Logic

The button appears when the scroll offset exceeds one screen height and scrolls the view to the latest message.

function judgeScrollOverOneScreen(e: Event) {
const scrollListDom = e.target as HTMLElement
const { height } = scrollListDom.getBoundingClientRect()
const { scrollHeight, scrollTop } = scrollListDom
if (height && scrollHeight) { isScrollOverOneScreen.value = scrollTop < scrollHeight - 2 * height }
}

Message Input Features

The input toolbar integrates emoji picker, image/file/video upload, voice/video call, and a rich‑text editor based on @tiptap/vue-3 . Sending is handled by sendMessages , which creates and dispatches text, image, file, or video messages via the Tencent Cloud Chat SDK.

export const sendMessages = async (chat: ChatSDK, messageList: ITipTapEditorContent[], currentConversationID: string, beforeSend?: (msg: Message) => void) => {
for (const content of messageList) {
const options: MESSAGE_OPTIONS = { to: currentConversationID, conversationType: TencentCloudChat.TYPES.CONV_GROUP, payload: {}, needReadReceipt: false, onProgress: () => {} }
switch (content?.type) {
case 'text': /* create and send text */ break;
case 'image': /* create and send image */ break;
case 'file': /* create and send file */ break;
case 'video': /* create and send video */ break;
}
}
}

Toolbar Sub‑Features

Emoji picker uses a predefined list (e.g., [龇牙] ) mapped to image URLs.

Image, file, and video uploads are triggered by hidden input elements and emitted to the parent.

Voice and video calls leverage @tencentcloud/call-uikit-vue with dynamic sizing and minimization support.

Editor Keyboard Handling

Enter sends the message; Shift + Enter inserts a new paragraph.

const handleEnter = (e: any) => {
e?.preventDefault(); e?.stopPropagation();
if (e.keyCode === 13 && e.shiftKey) { editor?.commands?.insertContent('<p></p>') }
else if (e.keyCode === 13) { emits('sendMessage') }
}

Paste Handling for Images and Files

Paste events are intercepted; images are inserted as custom nodes, while files are rendered as canvas‑generated thumbnails.

const handleFilePaste = async (e: any) => {
e.preventDefault(); e.stopPropagation();
const files = e?.clipboardData?.files;
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.type.startsWith('image/')) {
const fileSrc = URL.createObjectURL(file);
editor?.commands?.insertContent({ type: 'custom-image', attrs: { src: fileSrc, alt: file?.name, title: file?.name, class: 'normal' } })
}
}
}

Exposed Editor Methods

The component exposes getEditorContent , addEmoji , resetEditor , and setEditorContent for external control.

defineExpose({ getEditorContent, addEmoji, resetEditor, setEditorContent })

Conclusion

The article demonstrates a full‑stack instant‑messaging UI built with Vue 3, TypeScript, and Tencent Cloud's IM SDK, covering component architecture, rendering logic, user interaction, and auxiliary features such as file handling and voice/video calling, providing a solid reference for developers building similar chat solutions.

frontendTypeScriptVueComponent DesignInstant Messaging
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.