Quasar QChatMessage 组件企业级实现文档
Quasar QChatMessage 组件企业级实现文档
一、组件概述
QChatMessage 是基于 Quasar 框架和 Vue 3 组合式 API 开发的企业级聊天消息组件,支持文本/图片/文件消息展示、消息状态追踪、回复/转发/删除等交互功能,并通过智能分组、响应式设计和 TypeScript 类型安全,满足复杂聊天场景需求。
二、核心特性
- 多消息类型支持:文本、图片、文件消息统一渲染,支持自定义消息气泡样式
- 消息状态可视化:发送中/已发送/已送达/已读状态指示,实时反馈消息状态
- 智能消息分组:按日期(今天/昨天/具体日期)和时间间隔(>30分钟)自动分组,提升可读性
- 连续消息优化:同一用户2分钟内连续发送的消息自动合并,隐藏重复头像和名称
- 丰富交互功能:回复、转发、复制、删除消息,支持附件上传(文件/图片)
- TypeScript 全类型支持:完整类型定义,确保数据结构规范和开发体验
- 响应式设计:自适应不同屏幕尺寸,消息气泡和操作菜单布局自动调整
三、类型定义
3.1 核心类型(types/chat.ts)
// types/chat.ts
export interface User {
id: string; // 用户唯一标识
name: string; // 用户名(显示用)
avatar: string; // 头像URL
online?: boolean; // 在线状态(可选)
}
export type MessageStatus = 'sending' | 'sent' | 'delivered' | 'read' | 'error'; // 消息状态
export type MessageType = 'text' | 'image' | 'file' | 'system'; // 消息类型
export interface ChatMessage {
id: string; // 消息唯一标识
content: string; // 消息内容(文本/文件路径/图片URL)
timestamp: Date; // 发送时间戳
sender: User; // 发送者信息
status: MessageStatus; // 消息状态
type: MessageType; // 消息类型
replyTo?: string; // 回复目标消息ID(可选)
fileName?: string; // 文件名(文件类型消息)
fileSize?: number; // 文件大小(字节,文件类型消息)
imageUrl?: string; // 图片URL(图片类型消息)
}
export interface ChatRoom {
id: string; // 聊天室ID
name: string; // 聊天室名称
participants: User[]; // 参与者列表
lastMessage?: ChatMessage;// 最后一条消息
unreadCount: number; // 未读消息数
isGroup: boolean; // 是否为群聊
}
四、组件实现
4.1 完整组件代码(ChatMessages.vue)
<template>
<div class="chat-container q-pa-md">
<!-- 聊天标题区域 -->
<div class="chat-header q-mb-md">
<div class="text-h5">企业聊天应用</div>
<q-badge color="green" v-if="isConnected">
在线 ({{ onlineUsers }}人在线)
</q-badge>
<q-badge color="red" v-else>
离线
</q-badge>
</div>
<!-- 聊天消息区域(滚动容器) -->
<div class="chat-messages" ref="messagesContainer">
<!-- 1. 日期分隔符(按天分组) -->
<div
v-for="(dateGroup, dateIndex) in groupedMessages"
:key="`date-${dateIndex}`"
class="date-separator"
>
<q-separator />
<div class="date-label text-center q-my-md">
<q-badge color="primary">
{{ formatDateSeparator(dateGroup.date) }} <!-- 今天/昨天/YYYY年MM月DD日 -->
</q-badge>
</div>
</div>
<!-- 2. 消息分组(按时间间隔 >30分钟) -->
<div
v-for="(messageGroup, groupIndex) in groupedMessages"
:key="`group-${groupIndex}`"
>
<!-- 时间分隔符(组内首条消息或间隔>30分钟显示) -->
<div
v-if="shouldShowTimeSeparator(messageGroup, groupIndex)"
class="time-separator text-center q-my-sm"
>
<q-badge color="grey-6" rounded>
{{ formatTimeSeparator(messageGroup.messages[0].timestamp) }} <!-- HH:mm -->
</q-badge>
</div>
<!-- 3. 消息列表 -->
<div
v-for="(message, msgIndex) in messageGroup.messages"
:key="message.id"
class="message-wrapper"
:class="{
'consecutive': isConsecutiveMessage(messageGroup.messages, msgIndex), // 连续消息样式
'first-in-group': msgIndex === 0 // 组内首条消息样式
}"
@mouseenter="hoveredMessage = message.id"
@mouseleave="hoveredMessage = null"
>
<!-- Quasar QChatMessage 核心组件 -->
<q-chat-message
:name="message.sender.name" <!-- 发送者名称 -->
:avatar="message.sender.avatar" <!-- 发送者头像 -->
:text="[message.content]" <!-- 消息内容(数组格式支持多行) -->
:stamp="formatMessageTime(message.timestamp)" <!-- 消息时间戳 -->
:sent="message.sender.id === currentUser.id" <!-- 是否为当前用户发送(右对齐) -->
:bg-color="message.sender.id === currentUser.id ? 'primary' : 'grey-4'" <!-- 气泡颜色 -->
text-color="white" <!-- 文本颜色 -->
size="8" <!-- 头像大小(8px*8=64px) -->
class="chat-message"
>
<!-- 4. 消息状态指示器(仅当前用户消息) -->
<template v-if="message.sender.id === currentUser.id" v-slot:stamp>
<div class="message-status">
{{ formatMessageTime(message.timestamp) }}
<q-icon
v-if="message.status === 'sent'"
name="done"
size="14px"
class="q-ml-xs"
/>
<q-icon
v-else-if="message.status === 'delivered'"
name="done_all"
size="14px"
class="q-ml-xs"
/>
<q-icon
v-else-if="message.status === 'read'"
name="done_all"
size="14px"
color="blue"
class="q-ml-xs"
/>
<q-icon
v-else-if="message.status === 'error'"
name="error"
size="14px"
color="red"
class="q-ml-xs"
/>
</div>
</template>
<!-- 5. 消息内容与操作菜单 -->
<template v-slot:default>
<div class="message-content">
<!-- 文本内容 -->
{{ message.content }}
<!-- 图片附件 -->
<div v-if="message.type === 'image'" class="image-attachment">
<q-img
:src="message.imageUrl"
style="max-width: 200px; max-height: 200px; border-radius: 8px;"
class="q-mt-xs"
/>
</div>
<!-- 文件附件 -->
<div v-if="message.type === 'file'" class="file-attachment">
<q-icon name="attach_file" size="16px" />
<span class="file-name">{{ message.fileName }}</span>
<span class="file-size">({{ formatFileSize(message.fileSize || 0) }})</span>
</div>
<!-- 6. 消息操作按钮(悬停显示) -->
<div v-if="hoveredMessage === message.id" class="message-actions">
<q-btn
flat
round
dense
icon="reply"
size="sm"
@click="replyToMessage(message)"
/>
<q-btn
flat
round
dense
icon="delete"
size="sm"
@click="deleteMessage(message.id)"
v-if="message.sender.id === currentUser.id" <!-- 仅自己可删除 -->
/>
<q-btn
flat
round
dense
icon="more_vert"
size="sm"
>
<q-menu> <!-- 更多操作菜单 -->
<q-list style="min-width: 100px">
<q-item clickable v-close-popup @click="copyMessage(message)">
<q-item-section>复制</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="forwardMessage(message)">
<q-item-section>转发</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
</template>
</q-chat-message>
</div>
</div>
</div>
<!-- 7. 回复消息预览 -->
<div v-if="replyingTo" class="reply-preview q-pa-xs bg-grey-2 rounded-borders">
<div class="row items-center">
<div class="col">
<div class="text-caption text-weight-bold">回复 {{ replyingTo.sender.name }}</div>
<div class="text-caption text-grey-8 text-ellipsis">
{{ truncateText(replyingTo.content, 50) }} <!-- 截断长文本 -->
</div>
</div>
<div class="col-auto">
<q-btn flat round dense icon="close" size="sm" @click="cancelReply" />
</div>
</div>
</div>
<!-- 8. 消息输入区域 -->
<div class="chat-input q-mt-md">
<div class="row items-center">
<!-- 附件按钮 -->
<div class="col-auto">
<q-btn flat round icon="attach_file" @click="toggleAttachmentMenu">
<q-menu v-model="showAttachmentMenu">
<q-list>
<q-item clickable @click="attachFile">
<q-item-section avatar>
<q-icon name="insert_drive_file" />
</q-item-section>
<q-item-section>文件</q-item-section>
</q-item>
<q-item clickable @click="attachImage">
<q-item-section avatar>
<q-icon name="image" />
</q-item-section>
<q-item-section>图片</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<!-- 输入框 -->
<div class="col">
<q-input
v-model="newMessage"
outlined
dense
placeholder="输入消息..."
@keypress.enter="sendMessage" <!-- 回车发送 -->
:disable="!isConnected" <!-- 离线时禁用 -->
>
<template v-slot:append>
<q-icon
name="send"
class="cursor-pointer"
@click="sendMessage"
:color="newMessage.trim() ? 'primary' : 'grey'" <!-- 有内容时高亮发送按钮 -->
/>
</template>
</q-input>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick, watch } from 'vue';
import { date, useQuasar } from 'quasar';
import { ChatMessage, User } from './types/chat'; // 导入类型定义
// 1. 组件接口定义(Props & Emits)
interface MessageGroup { // 内部消息分组类型
date: string; // 日期(YYYY-MM-DD)
messages: ChatMessage[]; // 该日期下的消息
}
// Props(外部传入参数)
const props = withDefaults(defineProps<{
initialMessages?: ChatMessage[]; // 初始消息列表
currentUser: User; // 当前用户信息
isConnected?: boolean; // 连接状态(在线/离线)
onlineUsers?: number; // 在线用户数
}>(), {
initialMessages: () => [],
isConnected: false,
onlineUsers: 0
});
// Emits(对外事件)
const emit = defineEmits<{
(e: 'send-message', message: Partial<ChatMessage>): void; // 发送消息
(e: 'delete-message', messageId: string): void; // 删除消息
(e: 'reply-to-message', message: ChatMessage, reply: string): void; // 回复消息
(e: 'forward-message', message: ChatMessage): void; // 转发消息
}>();
// 2. 依赖与状态初始化
const $q = useQuasar(); // Quasar工具(通知、对话框等)
const messages = ref<ChatMessage[]>(props.initialMessages); // 消息列表(响应式)
const newMessage = ref(''); // 输入框内容
const replyingTo = ref<ChatMessage | null>(null); // 当前回复目标消息
const hoveredMessage = ref<string | null>(null); // 悬停中的消息ID
const showAttachmentMenu = ref(false); // 附件菜单显示状态
const messagesContainer = ref<HTMLElement | null>(null); // 消息容器(滚动用)
// 3. 计算属性(消息分组逻辑)
const groupedMessages = computed((): MessageGroup[] => {
const groups: Record<string, ChatMessage[]> = {}; // 按日期分组的临时对象
// 按日期(YYYY-MM-DD)分组消息
messages.value.forEach(message => {
const dateKey = date.formatDate(message.timestamp, 'YYYY-MM-DD'); // 标准化日期
if (!groups[dateKey]) groups[dateKey] = [];
groups[dateKey].push(message);
});
// 转换为数组并按日期排序(升序)
return Object.entries(groups)
.map(([date, messages]) => ({ date, messages }))
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
});
// 4. 核心方法实现
// 发送消息
const sendMessage = (): void => {
if (!newMessage.value.trim()) return; // 空消息不发送
const messageData: Partial<ChatMessage> = {
content: newMessage.value.trim(),
timestamp: new Date(),
sender: props.currentUser,
status: 'sent',
type: 'text'
};
if (replyingTo.value) { // 如处于回复状态,附加回复目标ID
messageData.replyTo = replyingTo.value.id;
emit('reply-to-message', replyingTo.value, newMessage.value.trim()); // 触发回复事件
} else {
emit('send-message', messageData); // 触发发送事件
}
newMessage.value = ''; // 清空输入框
cancelReply(); // 重置回复状态
scrollToBottom(); // 滚动到底部
};
// 删除消息
const deleteMessage = (messageId: string): void => {
emit('delete-message', messageId); // 触发删除事件
};
// 回复消息
const replyToMessage = (message: ChatMessage): void => {
replyingTo.value = message; // 设置回复目标
nextTick(() => {
(document.querySelector('input') as HTMLInputElement)?.focus(); // 聚焦输入框
});
};
// 取消回复
const cancelReply = (): void => {
replyingTo.value = null;
};
// 复制消息内容
const copyMessage = (message: ChatMessage): void => {
navigator.clipboard.writeText(message.content)
.then(() => {
$q.notify({ type: 'positive', message: '消息已复制' }); // 复制成功通知
})
.catch(err => console.error('复制失败:', err));
};
// 转发消息
const forwardMessage = (message: ChatMessage): void => {
emit('forward-message', message); // 触发转发事件
$q.notify({ type: 'info', message: '转发功能待实现' });
};
// 附件处理(文件/图片)
const attachFile = (): void => {
showAttachmentMenu.value = false;
$q.notify({ type: 'info', message: '文件附件功能待实现' });
};
const attachImage = (): void => {
showAttachmentMenu.value = false;
$q.notify({ type: 'info', message: '图片附件功能待实现' });
};
// 切换附件菜单显示
const toggleAttachmentMenu = (): void => {
showAttachmentMenu.value = !showAttachmentMenu.value;
};
// 滚动到底部(新消息时自动滚动)
const scrollToBottom = (): void => {
nextTick(() => {
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
}
});
};
// 5. 辅助方法(格式化与判断逻辑)
// 格式化消息时间(HH:mm)
const formatMessageTime = (timestamp: Date): string => {
return date.formatDate(timestamp, 'HH:mm');
};
// 格式化日期分隔符(今天/昨天/YYYY年MM月DD日)
const formatDateSeparator = (dateStr: string): string => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const targetDate = new Date(dateStr);
if (date.formatDate(targetDate, 'YYYY-MM-DD') === date.formatDate(today, 'YYYY-MM-DD')) {
return '今天';
} else if (date.formatDate(targetDate, 'YYYY-MM-DD') === date.formatDate(yesterday, 'YYYY-MM-DD')) {
return '昨天';
} else {
return date.formatDate(targetDate, 'YYYY年MM月DD日');
}
};
// 格式化时间分隔符(HH:mm)
const formatTimeSeparator = (timestamp: Date): string => {
return date.formatDate(timestamp, 'HH:mm');
};
// 判断是否显示时间分隔符(组内首条消息或与前一组间隔>30分钟)
const shouldShowTimeSeparator = (group: MessageGroup, groupIndex: number): boolean => {
if (group.messages.length === 0) return false;
if (groupIndex === 0) return true; // 第一组始终显示
const prevGroup = groupedMessages.value[groupIndex - 1];
if (!prevGroup?.messages.length) return true;
const lastPrevTime = prevGroup.messages[prevGroup.messages.length - 1].timestamp;
const firstCurrentTime = group.messages[0].timestamp;
const timeDiff = firstCurrentTime.getTime() - lastPrevTime.getTime();
return timeDiff > 30 * 60 * 1000; // 间隔>30分钟显示
};
// 判断是否为连续消息(同一用户2分钟内发送)
const isConsecutiveMessage = (messages: ChatMessage[], index: number): boolean => {
if (index === 0) return false; // 第一条不是连续消息
const current = messages[index];
const previous = messages[index - 1];
const isSameSender = current.sender.id === previous.sender.id;
const timeDiff = current.timestamp.getTime() - previous.timestamp.getTime();
return isSameSender && timeDiff < 2 * 60 * 1000; // 2分钟内
};
// 格式化文件大小(Bytes/KB/MB/GB)
const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
};
// 截断长文本(超出maxLength显示...)
const truncateText = (text: string, maxLength: number): string => {
return text.length <= maxLength ? text : `${text.substring(0, maxLength)}...`;
};
// 5. 生命周期与监听
// 初始化时滚动到底部
onMounted(() => {
scrollToBottom();
});
// 监听消息列表变化,自动滚动到底部
watch(messages, () => {
scrollToBottom();
}, { deep: true }); // 深度监听数组变化
// 监听初始消息 props 变化,更新本地消息列表
watch(() => props.initialMessages, (newMessages) => {
messages.value = newMessages;
}, { immediate: true }); // 初始加载时立即执行
</script>
<style scoped lang="scss">
.chat-container {
height: 100%;
display: flex;
flex-direction: column;
max-width: 800px; // 限制最大宽度,提升大屏体验
margin: 0 auto; // 居中布局
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-messages {
flex: 1; // 占满剩余空间
overflow-y: auto; // 垂直滚动
padding: 8px;
scroll-behavior: smooth; // 平滑滚动
}
/* 日期分隔符样式 */
.date-separator {
position: relative;
.date-label {
position: relative;
z-index: 1; // 覆盖分隔线
:deep(.q-badge) {
padding: 4px 12px;
font-weight: normal;
}
}
}
/* 时间分隔符样式 */
.time-separator {
:deep(.q-badge) {
padding: 2px 8px;
opacity: 0.7; // 半透明
}
}
/* 消息容器样式 */
.message-wrapper {
margin-bottom: 4px;
&.consecutive { // 连续消息:隐藏重复头像和名称
:deep(.q-chat-message) {
margin-top: 2px;
.q-chat-message-name { display: none; } // 隐藏名称
.q-chat-message-avatar { visibility: hidden; } // 隐藏头像
.q-chat-message-stamp { margin-top: -4px; } // 调整时间戳位置
}
}
&.first-in-group { // 组内首条消息:增加顶部间距
margin-top: 12px;
}
}
/* 消息内容区域 */
.message-content {
position: relative; // 为操作按钮定位
}
/* 消息操作按钮(悬停显示) */
.message-actions {
position: absolute;
top: -24px; // 悬浮在消息上方
right: 8px;
background: white;
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
gap: 4px; // 按钮间距
padding: 2px;
}
/* 消息状态指示器(发送/已读等) */
.message-status {
display: flex;
align-items: center;
font-size: 0.75rem; // 小号字体
}
/* 文件附件样式 */
.file-attachment {
display: flex;
align-items: center;
margin-top: 4px;
padding: 4px 8px;
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
gap: 8px;
.file-name { font-weight: 500; }
.file-size { color: #666; font-size: 0.8em; }
}
/* 回复预览样式 */
.reply-preview {
margin-top: 8px;
border-left: 3px solid $primary; // 左侧高亮边框
}
/* 输入区域样式 */
.chat-input {
border-top: 1px solid #e0e0e0; // 顶部分隔线
padding-top: 16px;
}
/* 文本截断 */
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
五、使用示例(父组件集成)
5.1 父组件代码
<template>
<div class="q-pa-md" style="height: 100vh;">
<chat-messages
:initial-messages="messages"
:current-user="currentUser"
:is-connected="isConnected"
:online-users="onlineUsersCount"
@send-message="handleSendMessage"
@delete-message="handleDeleteMessage"
@reply-to-message="handleReplyToMessage"
@forward-message="handleForwardMessage"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import ChatMessages from './components/ChatMessages.vue'; // 导入聊天组件
import { ChatMessage, User } from './types/chat'; // 导入类型定义
// 1. 模拟数据
// 当前用户
const currentUser = ref<User>({
id: 'user-1',
name: '张三',
avatar: 'https://cdn.quasar.dev/img/avatar.png' // Quasar默认头像
});
// 消息列表
const messages = ref<ChatMessage[]>([]);
// 连接状态
const isConnected = ref(true);
// 在线用户数
const onlineUsersCount = ref(5);
// 2. 初始化加载历史消息
onMounted(() => {
// 模拟API调用加载历史消息
setTimeout(() => {
messages.value = [
{
id: 'msg-1',
content: '你好,这个项目进展如何?',
timestamp: new Date(Date.now() - 1000 * 60 * 10), // 10分钟前
sender: {
id: 'user-2',
name: '李四',
avatar: 'https://cdn.quasar.dev/img/avatar2.png'
},
status: 'read',
type: 'text'
},
{
id: 'msg-2',
content: '一切顺利,我已经完成了前端部分。',
timestamp: new Date(Date.now() - 1000 * 60 * 5), // 5分钟前
sender: currentUser.value,
status: 'read',
type: 'text'
},
{
id: 'msg-3',
content: '这是设计稿最终版,请查收。',
timestamp: new Date(Date.now() - 1000 * 60 * 3), // 3分钟前
sender: {
id: 'user-2',
name: '李四',
avatar: 'https://cdn.quasar.dev/img/avatar2.png'
},
status: 'delivered',
type: 'file',
fileName: 'final-design.pdf',
fileSize: 2048000 // 2MB
}
];
}, 500);
});
// 3. 事件处理
// 处理发送消息
const handleSendMessage = (messageData: Partial<ChatMessage>): void => {
// 生成新消息(模拟后端返回ID)
const newMessage: ChatMessage = {
id: `msg-${Date.now()}`,
content: messageData.content || '',
timestamp: new Date(),
sender: currentUser.value,
status: 'sending', // 初始状态为发送中
type: messageData.type || 'text',
...messageData
};
messages.value.push(newMessage);
// 模拟消息发送成功(后端确认)
setTimeout(() => {
const index = messages.value.findIndex(m => m.id === newMessage.id);
if (index !== -1) {
messages.value[index].status = 'sent'; // 更新为已发送
}
// 模拟对方回复
setTimeout(() => {
messages.value.push({
id: `msg-${Date.now()}`,
content: '太好了!后端API也快完成了。',
timestamp: new Date(),
sender: {
id: 'user-2',
name: '李四',
avatar: 'https://cdn.quasar.dev/img/avatar2.png'
},
status: 'delivered',
type: 'text'
});
}, 2000);
}, 1000);
};
// 处理删除消息
const handleDeleteMessage = (messageId: string): void => {
messages.value = messages.value.filter(msg => msg.id !== messageId);
};
// 处理回复消息
const handleReplyToMessage = (targetMessage: ChatMessage, replyContent: string): void => {
console.log(`回复消息 [${targetMessage.id}]:`, replyContent);
// 实际项目中可调用API发送带引用的回复
};
// 处理转发消息
const handleForwardMessage = (message: ChatMessage): void => {
console.log('转发消息:', message);
// 实际项目中可打开转发对话框选择接收人
};
</script>
六、组件接口说明
6.1 Props(输入参数)
|
参数名 |
类型 |
默认值 |
说明 |
|
initialMessages |
ChatMessage[] |
[] |
初始消息列表 |
|
currentUser |
User |
- |
当前用户信息(必填,用于判断消息发送方) |
|
isConnected |
boolean |
false |
连接状态(在线/离线,控制输入框禁用) |
|
onlineUsers |
number |
0 |
在线用户数(显示在标题栏) |
6.2 Emits(输出事件)
|
事件名 |
参数类型 |
说明 |
|
send-message |
Partial<ChatMessage> |
发送文本消息时触发,包含消息内容等 |
|
delete-message |
string(messageId) |
删除消息时触发,传递消息ID |
|
reply-to-message |
ChatMessage(target),string(reply) |
回复消息时触发,传递目标消息和回复内容 |
|
forward-message |
ChatMessage |
转发消息时触发,传递被转发消息 |
七、扩展建议
- 消息搜索:添加按内容/发送者/时间的搜索功能,结合 Quasar QInput 和过滤器实现
- 表情支持:集成表情选择器(如q-emoji-picker),支持消息中插入表情
- 消息引用:回复时显示完整引用消息内容,而非仅显示摘要
- 已读回执:群聊场景下显示“已读人员列表”,需后端配合维护消息已读状态
- 消息撤回:支持2分钟内撤回已发送消息,通过状态字段控制消息显示/隐藏
八、总结
QChatMessage 组件通过组合式 API 逻辑拆分、TypeScript 类型安全和企业级交互设计,提供了开箱即用的聊天消息解决方案。核心优势在于:
- 智能分组:按日期和时间间隔自动组织消息,提升可读性
- 丰富交互:覆盖回复、转发、删除等高频操作,支持附件上传
- 状态反馈:实时显示消息发送/已读状态,优化用户体验
通过扩展建议中的功能,可进一步满足复杂企业聊天场景需求。
浙公网安备 33010602011771号