快速集成

TUIKit 是基于 IM SDK 的一款 UI 组件库,可通过 UI 组件快速实现聊天、会话、搜索、关系链、群组等功能。本文介绍如何快速集成 TUIKit 并实现核心功能。

前提条件

Node.js v18 版本及以上,建议使用当前的 LTS v22 版本
React v18 版本

创建项目

创建一个新的名为 chat-app 的 React 项目(创建项目时选择 React 18,并且使用 TypeScript 模板),并按照脚手架提示完成项目的初始化。
npm create rsbuild@latest
# 初始化脚手架项目
cd chat-app
npm i
npm run dev

安装并引入组件

步骤1:安装依赖

通过 npm 方式下载 chat-uikit-react 并在项目中使用。
npm i @tencentcloud/chat-uikit-react

步骤2:引入组件

注意:
以下代码中未填入 SDKAppIDuserIDuserSig,需在 步骤3 中获取相关信息后进行替换。
为尊重表情设计版权,Chat Demo/TUIKit 工程中不包含大表情元素切图,正式上线商用前请您替换为自己设计或拥有版权的其他表情包。下图所示默认的小黄脸表情包版权归腾讯云所有,您可以通过升级至 Chat 专业版 Plus 和企业版 免费使用该表情包。



复制以下 App.tsx 代码并替换原有的 App.tsx 中的内容。
复制以下 App.css 代码并替换同级目录的 App.css 样式文件。
App.tsx
App.css
import {
UIKitProvider,
useLoginState,
LoginStatus,
ConversationList,
Chat,
ChatHeader,
MessageList,
MessageInput,
ContactList,
ContactInfo,
useUIKit,
useConversationListState,
} from "@tencentcloud/chat-uikit-react";
import { useEffect, useState } from "react";
import './App.css';

function App() {
// 语言支持 en-US(default) / zh-CN / ja-JP / ko-KR / zh-TW
// 主题支持 light(default) / dark
return (
<UIKitProvider theme={'light'} language={'en-US'}>
<ChatApp />
</UIKitProvider>
);
}

function ChatApp() {
const [activeTab, setActiveTab] = useState('chats');
const { language } = useUIKit();
const texts = language === 'zh-CN'
? { chats: '会话', contacts: '联系人', emptyTitle: '暂无会话', emptySub: '选择一个会话开始聊天', error: '请检查 SDKAppID, userID, userSig, 通过开发人员工具(F12)查看具体的错误信息', loading: '登录中...' }
: { chats: 'Chats', contacts: 'Contacts', emptyTitle: 'No conversation', emptySub: 'Select a conversation to start chatting', error: 'Please check the SDKAppID, userID, and userSig. View the specific error information through the developer tools (F12).', loading: 'Logging in...'};

const { status } = useLoginState({
SDKAppID: 0, // type: number
userID: '', // type: string
userSig: '', // type: string
})
const { setActiveConversation } = useConversationListState();

useEffect(() => {
async function init() {
// 你可以换成其他已创建的 userID
const userID = 'administrator';
const conversationID = `C2C${userID}`;
setActiveConversation(conversationID);
}

if (status === LoginStatus.SUCCESS) {
init();
}
}, [status]);

if (status === LoginStatus.ERROR) {
return (
<div className="loading-container">
<div className="loading-spinner"></div>
<div className="loading-text">{texts.error}</div>
</div>
)
}

if (status !== LoginStatus.SUCCESS) {
return (
<div className="loading-container">
<div className="loading-spinner"></div>
<div className="loading-text">{texts.loading}</div>
</div>
)
}

return (
<div className="chat-app">
<ul className="vertical-tabs">
<li
className={`tab-item ${activeTab === 'chats' ? 'active' : ''}`}
onClick={() => {setActiveTab('chats')}}
>
{texts.chats}
</li>
<li
className={`tab-item ${activeTab === 'contacts' ? 'active' : ''}`}
onClick={() => {setActiveTab('contacts')}}
>
{texts.contacts}
</li>
</ul>
{
activeTab === 'chats' && (
<>
<ConversationList style={{ flex: '0 0 300px'}}/>
<Chat
className="chat-box"
PlaceholderEmpty={
<div className="empty-chat">
<div className="empty-icon">💬</div>
<div className="empty-title">{texts.emptyTitle}</div>
<div className="empty-subtitle">{texts.emptySub}</div>
</div>
}
>
<ChatHeader />
<MessageList />
<MessageInput />
</Chat>
</>
)
}
{
activeTab === 'contacts' && (
<>
<ContactList style={{ flex: '0 0 300px'}}/>
<ContactInfo
style={{ flex: '1'}}
onSendMessage={() => {setActiveTab('chats')}}
onEnterGroup={() => {setActiveTab('chats')}}
/>
</>
)
}
</div>
);
}

export default App;
body {
margin: 0;
padding: 20px;
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
box-sizing: border-box;
}

.chat-app {
height: calc(100vh - 40px);
max-width: 1400px;
margin: 0 auto;
display: flex;
flex-direction: row;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(0, 0, 0, 0.1);
overflow: hidden;
backdrop-filter: blur(10px);
}

.vertical-tabs {
list-style: none;
margin: 0;
padding: 0;
width: 100px;
background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);
border-right: 1px solid rgba(0, 0, 0, 0.08);
}

.tab-item {
padding: 16px 20px;
cursor: pointer;
background: transparent;
color: #495057;
font-size: 14px;
text-align: center;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}

.tab-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 0;
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
border-radius: 0 3px 3px 0;
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.tab-item:hover {
background: rgba(102, 126, 234, 0.08);
color: #667eea;
}

.tab-item.active {
background: linear-gradient(90deg, rgba(102, 126, 234, 0.15) 0%, rgba(102, 126, 234, 0.05) 100%);
color: #667eea;
}

.tab-item.active::before {
height: 60%;
}

.chat-box {
flex: 1;
border-left: 1px solid rgba(0, 0, 0, 0.08);
}

.empty-chat {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #adb5bd;
gap: 12px;
text-align: center;
border-left: 1px solid rgba(0, 0, 0, 0.08);
}

.empty-icon {
font-size: 64px;
opacity: 0.3;
animation: float 3s ease-in-out infinite;
}

.empty-title {
font-size: 16px;
font-weight: 600;
color: #6c757d;
}

.empty-subtitle {
font-size: 14px;
color: #868e96;
}

@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}

.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
gap: 24px;
}

.loading-spinner {
width: 60px;
height: 60px;
border: 4px solid rgba(255, 255, 255, 0.2);
border-top-color: #ffffff;
border-radius: 50%;
animation: spin 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.loading-text {
color: #ffffff;
font-size: 18px;
font-weight: 500;
letter-spacing: 0.5px;
animation: pulse 2s ease-in-out infinite;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}


步骤3:获取 SDKAppID、userID 和 userSig

在之前复制的 App.tsx 代码中,登录鉴权信息是空缺的,您还需要在 useLoginState hook 中填写您的腾讯云应用的鉴权信息,如下所示:
参数
类型
说明
SDKAppID
Number
在创建的音频和视频应用程序中,作为唯一标识符为。可以在Tencent RTC 控制台中创建一个应用来获取SDKAppID。
userID
String
用户唯一标识符,可以在控制台中创建 2~3 个预设账号,用于互相发送消息,仅允许包含大小写字母(a-z, A-Z)、数字(0-9)、下划线和连字符。
secretKey
String
在创建的音频和视频应用程序中,SDKSecretKey,作为密钥请妥善保存。可以在Tencent RTC 控制台中获取。
userSig
String
用于用户登录认证的安全保护签名,用于确认用户的身份并防止恶意攻击者窃取您的云服务使用权限。
可单击 即时通信 IM 控制台 > 开发工具 > UserSig 工具,填写创建的 userID,即可生成 userSig。
1. 登录 即时通信 IM 控制台,在应用管理页面,单击创建新应用。如果您已有应用,可省略创建应用过程。

2. 应用管理页面的 SDKAppID 列获取 SDKAppID 和密钥信息。

注意:
查看密钥信息需要验证身份。
密钥信息为敏感信息,为防止他人盗用,请妥善保管,谨防泄露。
3. 进入用户管理页面,创建 2~3 个测试账号,用于体验单聊能力和群聊能力。

4. userSig 信息,可单击 即时通信 IM 控制台 > 开发工具 > UserSig 工具,填写创建的 userID,即可生成 userSig。
用户签名说明:
开发环境:如果您在本地运行演示并进行调试开发,可以使用调试文件中的 genTestUserSig 函数(参见步骤 3.2)来生成 'userSig'。在此方法中,SDKSecretKey 易受反编译和逆向工程攻击。一旦您的密钥泄露,攻击者即可窃取您的腾讯云流量。
生产环境:如果您的项目即将上线,请使用服务端生成用户签名 的方法。

运行和测试

运行命令如下:
npm run dev
注意:
1. 请确保 步骤3 代码中 SDKAppIDuserIDuserSig 均已成功输入,如未替换将会导致项目表现异常。
2. userIDuserSig 为一一对应关系,具体参见 生成 UserSig
3. 如遇到项目启动失败,请检查 开发环境要求 是否满足。

发送您的第一条消息

体验单聊功能:搜索好友并发送您的第一条消息



体验群聊功能:创建群聊并发送一条消息



常见问题

什么是 UserSig?

UserSig 是用户登录 Chat 的密码,其本质是对 UserID 等信息加密后得到的密文。

如何生成 UserSig?

UserSig 签发方式是将 UserSig 的计算代码集成到您的服务端,并提供面向项目的接口,在需要 UserSig 时由您的项目向业务服务器发起请求获取动态 UserSig。更多详情请参见 服务端生成 UserSig
注意:
本文示例代码采用的获取 UserSig 的方案是在客户端代码中配置 SECRETKEY,该方法中 SECRETKEY 很容易被反编译逆向破解,一旦您的密钥泄露,攻击者就可以盗用您的腾讯云流量,因此该方法仅适合本地跑通功能调试。 正确的 UserSig 签发方式请参见上文。

交流与反馈

加入 Telegram 技术交流群组WhatsApp 交流群,享有专业工程师的支持,解决您的难题。

相关文档

UIKit 相关: