npm create vite@latest
◇ Project name:│ chat-integration-react│◇ Select a framework:│ React│◇ Select a variant:│ TypeScript + React Compiler│◆ Install with npm and start now?│ ● Yes / ○ No
npm i @tencentcloud/chat-uikit-react
App.tsx 代码并替换原有的 App.tsx 中的内容。import { useEffect, useLayoutEffect, useState, useMemo } from "react";import {UIKitProvider,useLoginState,LoginStatus,ConversationList,Chat,ChatHeader,MessageList,MessageInput,ContactList,ContactInfo,ChatSetting,Search,VariantType,Avatar,useUIKit,useConversationListState,} from "@tencentcloud/chat-uikit-react";import { IconChat, IconUsergroup, IconBulletpoint, IconSearch } from "@tencentcloud/uikit-base-component-react";function App() {// 语言支持 en-US(default) / zh-CN / ja-JP / ko-KR / zh-TW// 主题支持 light(default) / darkreturn (<UIKitProvider theme={'dark'} language={'en-US'}><ChatApp /></UIKitProvider>);}function ChatApp() {const [activeTab, setActiveTab] = useState<'conversations' | 'contacts'>('conversations');const [isChatSettingShow, setIsChatSettingShow] = useState(false);const [isSearchInChatShow, setIsSearchInChatShow] = useState(false);const { language, theme } = useUIKit();const isDark = theme === 'dark';const texts = useMemo(() =>language === 'zh-CN'? { emptyTitle: '暂无会话', emptySub: '选择一个会话开始聊天', error: '请检查 SDKAppID, userID, userSig, 通过开发人员工具(F12)查看具体的错误信息', loading: '登录中...' }: { 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...' },[language]);const { status } = useLoginState({SDKAppID: 0, // number 类型userID: '', // string 类型userSig: '', // string 类型});const { setActiveConversation, activeConversation } = useConversationListState();// 初始化默认会话 可删除useEffect(() => {if (status === LoginStatus.SUCCESS) {const userID = 'administrator';const conversationID = `C2C${userID}`;setActiveConversation(conversationID);}}, [status, setActiveConversation]);// 切换会话时自动关闭侧边栏useLayoutEffect(() => {setIsChatSettingShow(false);setIsSearchInChatShow(false);}, [activeConversation?.conversationID]);if (status === LoginStatus.ERROR) {return (<div className="loading-container is-error"><div className="loading-brand"><div className="loading-brand-icon"><svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg></div><span className="loading-brand-name">Chat</span></div><div className="error-icon">!</div><div className="loading-text">{texts.error}</div></div>);}if (status !== LoginStatus.SUCCESS) {return (<div className="loading-container"><div className="loading-brand"><div className="loading-brand-icon"><svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg></div><span className="loading-brand-name">Chat</span></div><div className="loading-spinner"></div><div className="loading-text">{texts.loading}</div><div className="loading-progress-bar"><div className="loading-progress-bar-inner"></div></div></div>);}return (<div className={`chat-layout ${isDark ? 'dark' : ''}`}>{/* 左侧导航 */}<SideTab activeTab={activeTab} onTabChange={setActiveTab} />{/* 中间列表 会话列表-联系人列表 */}<div className="conversation-list-panel">{activeTab === 'conversations' ? <ConversationList /> : <ContactList className="contact-list" />}</div>{/* 右侧聊天 */}{activeTab === 'conversations' && (<ChatclassName="chat-content-panel"PlaceholderEmpty={<div className="empty-placeholder"><div className="empty-icon">💬</div><div className="empty-title">{texts.emptyTitle}</div><div className="empty-subtitle">{texts.emptySub}</div></div>}><div className="chat-main"><ChatHeaderChatHeaderRight={<div className="header-actions"><buttonclassName="icon-button"onClick={() => setIsSearchInChatShow(!isSearchInChatShow)}><IconSearch size="20px" /></button><buttonclassName="icon-button"onClick={() => setIsChatSettingShow(!isChatSettingShow)}><IconBulletpoint size="20px" /></button></div>}/><MessageList /><MessageInput /></div>{/* 会话内搜索侧边栏 */}{isSearchInChatShow && (<div className="chat-sidebar-search"><div className="chat-sidebar-header"><span className="chat-sidebar-title">群搜索</span><buttonclassName="icon-button"onClick={() => setIsSearchInChatShow(false)}>✕</button></div><Search variant={VariantType.EMBEDDED} /></div>)}{/* 聊天设置侧边栏 */}{isChatSettingShow && (<div className="chat-float-sidebar"><ChatSetting onClose={() => setIsChatSettingShow(false)} /></div>)}</Chat>)}{/* 联系人详情 */}{activeTab === 'contacts' && (<ContactInfoclassName="contact-detail-panel"onSendMessage={() => setActiveTab('conversations')}onEnterGroup={() => setActiveTab('conversations')}/>)}</div>);}// SideTab 组件:左侧导航栏interface SideTabProps {activeTab: 'conversations' | 'contacts';onTabChange: (tab: 'conversations' | 'contacts') => void;}function SideTab({ activeTab, onTabChange }: SideTabProps) {const { theme } = useUIKit();const { loginUserInfo } = useLoginState();const isDark = theme === 'dark';return (<div className={`side-tab ${isDark ? 'dark' : ''}`}>{/* 用户头像 */}<div className="avatar-wrapper"><Avatar src={loginUserInfo?.avatarUrl} /><div className="tooltip"><div className="tooltip-name">{loginUserInfo?.userName || loginUserInfo?.userId || '未命名'}</div><div className="tooltip-id">ID: {loginUserInfo?.userId}</div></div></div>{/* Tab 切换 */}<div className="tabs"><divclassName={`tab-item ${activeTab === 'conversations' ? 'active' : ''}`}onClick={() => onTabChange('conversations')}title="会话"><IconChat size="24px" /></div><divclassName={`tab-item ${activeTab === 'contacts' ? 'active' : ''}`}onClick={() => onTabChange('contacts')}title="联系人"><IconUsergroup size="24px" /></div></div></div>);}export default App;
index.css 代码并替换同级目录的 src/index.css 样式文件。body { margin: 0; padding: 0; font-family: Inter, Avenir, Helvetica, Arial, sans-serif; min-height: 100vh; box-sizing: border-box; } #root { height: 100vh; display: flex; justify-content: center; align-items: center; } .chat-layout { height: 60vh; aspect-ratio: 16 / 9; margin: 0 auto; display: flex; border-radius: 16px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1); overflow: hidden; backdrop-filter: blur(10px); } @media (max-width:1920px) { .chat-layout { height: 80vh; } } .conversation-list-panel { width: 300px; display: flex; flex-direction: column; overflow: hidden; } .contact-list { border-radius: 0; } .chat-content-panel { flex-direction: row !important; flex: 1; border-left: 1px solid var(--uikit-stroke-color-primary); } .contact-detail-panel { flex: 1; } .icon-button { padding: 4px 6px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; border-radius: 4px; font-size: 16px; cursor: pointer; transition: background-color 0.2s; color: var(--uikit-text-color-primary); } .icon-button:hover { background-color: var(--uikit-button-color-secondary-hover); } .icon-button:active { background-color: var(--uikit-button-color-secondary-active); } .header-actions { display: flex; gap: 8px; margin-left: 8px; color: var(--uikit-text-color-link); } .message-toolbar { display: flex; justify-content: space-between; align-items: center; } .message-toolbar-actions { display: flex; align-items: center; gap: 12px; } .chat-main { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .chat-sidebar-search { border-left: 1px solid var(--uikit-stroke-color-primary); display: flex; flex-direction: column; flex: 0 0 300px; } .chat-float-sidebar { position: absolute; right: 0; top: 0; bottom: 0; min-width: 300px; max-width: 400px; display: flex; flex-direction: column; background-color: var(--uikit-bg-color-operate); box-shadow: -2px 0 8px rgba(0, 0, 0, 0.04), -4px 0 16px rgba(0, 0, 0, 0.06), -8px 0 32px rgba(0, 0, 0, 0.08); overflow: auto; z-index: 1000; } .chat-sidebar-header { position: sticky; top: 0; display: flex; align-items: center; justify-content: space-between; padding: 20px; background-color: var(--uikit-bg-color-operate); border-bottom: 1px solid var(--uikit-stroke-color-primary); z-index: 10; } .chat-sidebar-title { font-size: 16px; font-weight: 500; color: var(--uikit-text-color-primary); } .empty-placeholder { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background-color: var(--uikit-bg-color-operate); border-left: 1px solid var(--uikit-stroke-color-primary); color: #adb5bd; } .empty-icon { font-size: 64px; opacity: 0.3; } .empty-title { font-size: 16px; font-weight: 600; color: #6c757d; } .empty-subtitle { font-size: 14px; color: #868e96; } .loading-container { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; gap: 20px; } .loading-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; } .loading-brand-icon { width: 36px; height: 36px; border-radius: 8px; background: linear-gradient(135deg, #147aff, #6db3f2); display: flex; align-items: center; justify-content: center; } .loading-brand-icon svg { width: 20px; height: 20px; fill: #fff; } .loading-brand-name { font-size: 18px; font-weight: 600; color: #1a1a1a; letter-spacing: 0.2px; } .loading-spinner { width: 36px; height: 36px; border: 3px solid #e8ecf1; border-top-color: #147aff; border-radius: 50%; animation: spin 0.8s linear infinite; } .loading-text { color: #5f6368; font-size: 14px; font-weight: 400; line-height: 1.5; text-align: center; max-width: 360px; } .loading-progress-bar { width: 200px; height: 3px; background-color: #e8ecf1; border-radius: 3px; overflow: hidden; margin-top: 4px; } .loading-progress-bar-inner { width: 40%; height: 100%; background: linear-gradient(90deg, #147aff, #6db3f2); border-radius: 3px; animation: progress-slide 1.6s ease-in-out infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes progress-slide { 0% { transform: translateX(-100%); } 50% { transform: translateX(250%); } 100% { transform: translateX(600%); } } /* ===== Error ===== */ .loading-container.is-error { background-color: #f7f8fa; } .loading-container.is-error .loading-spinner { display: none; } .error-icon { width: 48px; height: 48px; border-radius: 50%; background-color: #fff2f0; border: 1px solid #ffccc7; display: flex; align-items: center; justify-content: center; font-size: 22px; color: #ff4d4f; flex-shrink: 0; } .loading-container.is-error .loading-text { color: #5f6368; font-size: 13px; line-height: 1.6; } .icon-image-effort { display: none; } .side-tab { width: 72px; height: 100%; background: var(--uikit-bg-color-function); display: flex; flex-direction: column; align-items: center; padding: 20px 0; transition: background 0.3s; } .avatar-wrapper { position: relative; margin-bottom: 24px; cursor: pointer; } .avatar-wrapper:hover .tui-avatar { transform: scale(1.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .tooltip { position: absolute; left: 60px; top: 50%; transform: translateY(-50%); padding: 8px 12px; background: rgba(0, 0, 0, 0.85); color: #fff; border-radius: 6px; white-space: nowrap; opacity: 0; visibility: hidden; pointer-events: none; transition: all 0.3s; z-index: 1000; } .tooltip::before { content: ''; position: absolute; left: -6px; top: 50%; transform: translateY(-50%); border: 6px solid transparent; border-right-color: rgba(0, 0, 0, 0.85); } .avatar-wrapper:hover .tooltip { opacity: 1; visibility: visible; } .tooltip-name { font-size: 14px; font-weight: 500; margin-bottom: 4px; } .tooltip-id { font-size: 12px; opacity: 0.8; } .tabs { display: flex; flex-direction: column; gap: 16px; } .tab-item { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; border-radius: 12px; cursor: pointer; transition: all 0.3s; color: var(--uikit-text-color-primary); } .tab-item:hover { background: rgba(0, 0, 0, 0.05); } .tab-item.active { background: var(--uikit-button-color-primary-default); color: var(--uikit-text-color-button); } .call-kit { position: fixed !important; top: 50%; left: 50%; z-index: 999; transform: translate(-50%, -50%); max-width: 800px; max-height: 600px; }
App.tsx 代码中,登录鉴权信息是空缺的,您还需要在 useLoginState hook 中填写您的腾讯云应用的鉴权信息,如下所示:参数 | 类型 | 说明 |
SDKAppID | Number | |
userID | String | |
secretKey | String | |
userSig | String | 用于用户登录认证的安全保护签名,用于确认用户的身份并防止恶意攻击者窃取您的云服务使用权限。 |



genTestUserSig 函数(参见步骤 3.2)来生成 'userSig'。在此方法中,SDKSecretKey 易受反编译和逆向工程攻击。一旦您的密钥泄露,攻击者即可窃取您的腾讯云流量。npm run dev
SDKAppID、userID 和 userSig 均已成功输入,如未替换将会导致项目表现异常。userID 和 userSig 为一一对应关系,具体参见 生成 UserSig。@trtc/calls-uikit-react 依赖npm install @trtc/calls-uikit-react
@trtc/calls-uikit-react 导出 TUICallKit,并挂载到 DOM 节点上。src/App.tsx 文件中继续补充下面的代码:// src/App.tsximport { TUICallKit } from '@trtc/calls-uikit-react';function App() {return (<UIKitProvider>// 导入 TUICallKit 并添加到代码中<TUICallKit className="call-kit" /><ChatApp /></UIKitProvider>);}
<ChatHeader /> 上启用 enableCall 属性<ChatHeader enableCall={true} />



<ChatSetting /> 封装成全屏抽屉组件。但是核心组件内部已经存在的组件暂时不支持修改。import { Drawer } from 'antd';const [isChatSettingShow, setIsChatSettingShow] = useState(false);function onChatSettingClose() {setIsChatSettingShow(false);}<Drawertitle="设置"onClose={onChatSettingClose}open={isChatSettingShow}><ChatSetting /></Drawer>

文档反馈