ChatHeader

最近更新时间:2025-09-30 16:00:32

我的收藏

概述

ChatHeader 聊天头部组件是聊天界面的顶部导航组件,用于显示当前会话的基本信息。能够展示会话标题、头像、在线状态、输入状态提示等信息。组件采用容器组件模式,提供了灵活的插槽机制用于自定义左右两侧的操作区域。

Props 速查表

字段
类型
默认值
描述
title
string
undefined
自定义标题,未提供时使用会话信息。
avatarUrl
string
undefined
自定义头像图片 URL。
boolean
false
是否显示用户状态。
bool
false
是否展示音视频通话按钮

Slot 速查表

名称
描述
ChatHeader 左侧自定义内容区域。
ChatHeader 右侧自定义内容区域。


Props 详细介绍

title

类型string
描述:自定义会话标题,当不提供此属性时,组件会自动根据会话类型显示相应的标题(单聊显示用户昵称或备注,群聊显示群名称),默认值为 undefined

avatarUrl

类型string
描述:自定义头像图片的 URL 地址,当不提供此属性时,组件会自动根据会话类型显示相应的头像(单聊显示用户头像,群聊显示群头像),默认值为 undefined

enableUserStatus

类型boolean
描述:是否启用并显示用户在线状态功能,仅在单聊模式下生效,开启后会显示用户的在线/离线状态,默认值为 false

enableCall

类型boolean
描述:是否启用并显示音视频通话按钮,开启后会显示音视频通话的按钮,默认值为 false
注意:显示按钮并不意味着可以发起通话,发起音视频通话的前提是集成了 CallKit,集成 CallKit 可以参考集成 TUICallKit

Slots 详细介绍

ChatHeaderLeft

描述:头部左侧自定义内容区域插槽,通常用于放置返回按钮、菜单按钮等导航控件。

示例: 自定义左侧导航区域

<template>
<ChatHeader>
<template #ChatHeaderLeft>
<!-- 返回按钮, 清除激活的会话 -->
<button
class="nav-button"
@click="handleBack"
>
⬅️
</button>
</template>
</ChatHeader>
</template>

<script setup lang="ts">
import { ChatHeader, useConversationListState } from '@tencentcloud/chat-uikit-vue3';

const { setActiveConversation } = useConversationListState();

const handleBack = () => {
// 设置激活的会话ID为空
setActiveConversation('');
};
</script>

<style scoped>
.header-left-actions {
display: flex;
align-items: center;
gap: 8px;
}

.nav-button {
width: 32px;
height: 32px;
border: none;
background: transparent;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}

.nav-button:hover {
background-color: rgba(0, 0, 0, 0.05);
}
</style>

ChatHeaderRight

描述:头部右侧自定义内容区域插槽,通常用于放置功能按钮、操作菜单等控件。

示例: 通过 ChatHeaderRight 插槽 和抽屉组件集成 ChatSetting 组件

App.vue
Drawer.vue
<template>
<div class="chat-container">
<Chat>
<ChatHeader>
<template #ChatHeaderRight>
<!-- 设置按钮,点击打开聊天设置面板 -->
<button
class="nav-button"
@click="setIsSettingOpen(true)"
>
⚙️
</button>
</template>
</ChatHeader>
<MessageList />
<MessageInput />
</Chat>
<Drawer
:open="isChatSettingOpen"
@close="setIsSettingOpen(false)"
>
<ChatSetting style="flex: 1;" />
</Drawer>
</div>
</template>

<script setup lang="ts">
import {
Chat,
ChatHeader,
MessageList,
MessageInput,
ChatSetting
} from '@tencentcloud/chat-uikit-vue3';
import { Drawer } from './components/Drawer.vue';

// 状态管理
const isChatSettingOpen = ref(false);

const setIsSettingOpen = (isOpen: boolean) => {
isChatSettingOpen.value = isOpen;
};
</script>

<style scoped>
.chat-container {
display: flex;
flex-direction: column;
height: 100vh;
}
</style>

<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import type { PropType } from 'vue';

const props = defineProps({
open: { type: Boolean, required: true },
placement: {
type: String as PropType<'right' | 'left' | 'bottom'>,
default: 'right',
},
duration: { type: Number, default: 300 },
container: {
type: [String, Object] as PropType<string | HTMLElement>,
default: 'body',
},
zIndex: { type: Number, default: 1000 },
maskClosable: { type: Boolean, default: true },
});

const emit = defineEmits<{
(e: 'close'): void;
}>();

const isRendered = ref(false);
const isVisible = ref(false);
const isAnimating = ref(false);

const containerTarget = computed(() => props.container ?? 'body');
const cssVars = computed(() => ({
'--duration': `${props.duration}ms`,
'--z-index': String(props.zIndex),
}));

watch(
() => props.open,
async (nextOpen) => {
if (nextOpen) {
// open drawer
if (!isRendered.value) {
isRendered.value = true;
}
// wait for DOM render
await nextTick();
// force repaint, ensure initial state is applied
requestAnimationFrame(() => {
isVisible.value = true;
isAnimating.value = true;
});
} else {
// close drawer
isVisible.value = false;
isAnimating.value = true;
}
},
{ immediate: true },
);

function onMaskClick() {
if (props.maskClosable) {
emit('close');
}
}

function handlePanelTransitionEnd(e: TransitionEvent) {
if (e.propertyName !== 'transform') {
return;
}
isAnimating.value = false;
if (!isVisible.value) {
isRendered.value = false;
}
}

function handleMaskTransitionEnd(e: TransitionEvent) {
if (e.propertyName !== 'opacity') {
// mask animation end
}
}
</script>

<template>
<Teleport :to="containerTarget">
<div
v-if="isRendered"
:class="[$style.wrapper, isVisible ? $style['wrapper-open'] : '']"
:style="cssVars"
>
<div
:class="[$style.mask, isVisible ? $style['mask-open'] : '']"
@click="onMaskClick"
@transitionend="handleMaskTransitionEnd"
/>
<div
:class="[
$style.panel,
$style[`from-${props.placement}`],
isVisible ? $style['panel-open'] : ''
]"
@click.stop
@transitionend="handlePanelTransitionEnd"
>
<slot />
</div>
</div>
</Teleport>
</template>

<style module lang="scss">
.wrapper{position:fixed;inset:0;z-index:var(--z-index);pointer-events:none;}.wrapper-open{pointer-events:auto;}.mask{position:absolute;inset:0;background:rgba(0,0,0,0.45);opacity:0;transition:opacity var(--duration) ease;pointer-events:auto;}.mask-open{opacity:1;}.panel{position:absolute;background:var(--bg-color-operate);max-width:100%;max-height:100%;transition:transform var(--duration) ease;pointer-events:auto;box-shadow:0 8px 16px rgba(0,0,0,0.15);display:flex;flex-direction:row;}.from-right{top:0;right:0;height:100%;width:320px;transform:translateX(100%);}.from-left{top:0;left:0;height:100%;width:320px;transform:translateX(-100%);}.from-bottom{left:0;bottom:0;width:100%;height:40%;transform:translateY(100%);}.panel-open.from-right,.panel-open.from-left,.panel-open.from-bottom{transform:translate(0,0);}
</style>



集成 TUICallKit

需要安装 @tencentcloud/call-uikit-vue 并在项目中导入,更多信息参考音视频通话(含 UI)
<script lang="ts" setup>
import { Teleport } from 'vue';
import { TUICallKit } from '@tencentcloud/call-uikit-vue';
<script>

<template>
<UIKitProvider>
<Teleport to="body">
<TUICallKit
style="
position: fixed;
width: 600px;
height: 400px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
"
/>
</Teleport>
// other code ...
</UIKitProvider>
<template>

相关文档

交流与反馈

如遇任何问题,可联系 官网售后 反馈,享有专业工程师的支持,解决您的难题。