在 AI 对话产品流行的今天,一个流畅、直观的聊天界面是提升用户体验的关键。本文将分享一个基于 Vue 3 + Element Plus 开发的智能聊天界面实现,包含实时流式响应、对话历史管理、动态标题生成等核心功能,并详解其技术细节与实现思路。

项目介绍
这个聊天界面模仿了主流 AI 助手的交互模式,具备以下特点:
- 实时流式响应:通过 SSE(Server-Sent Events)实现 AI 回复的 "打字机" 效果
- 完整的对话生命周期管理:支持新对话创建、历史记录加载与展示
- 增强用户体验的辅助功能:对话分享、收藏、动态标题
- 响应式设计:适配不同屏幕尺寸
- 清晰的视觉层次:区分用户与 AI 消息,优化布局与交互
功能亮点
- 流式响应(SSE):无需等待完整回复,AI 边生成边展示,减少用户等待感
- 对话历史管理:自动存储对话记录,区分用户与 AI 消息
- 动态标题:根据第一条用户消息自动生成对话标题
- 实用辅助功能:支持对话分享(复制链接)、收藏(浏览器书签)
- 响应式布局:在手机与桌面设备上均有良好表现
核心代码解析
整体架构
界面采用经典的三部分布局:顶部导航区、中间聊天内容区、底部输入区。代码使用 Vue 3 的<script setup>
组合式 API,逻辑清晰,易于维护。
1. 模板结构(Template)
模板部分定义了界面的 HTML 结构,通过 Element Plus 组件构建 UI,核心代码如下:
<template>
<!-- 整体容器 -->
<div class="doubao-layout">
<!-- 1. 顶部导航区 -->
<header class="doubao-header">
<div class="header-inner">
<!-- 标题与新对话 -->
<div class="left-group">
<el-button type="primary" icon="Plus" size="mini" @click="handleNewChat">
新对话
</el-button>
<h2 class="header-title">{{ chatTitle }}</h2>
</div>
<!-- 操作与头像 -->
<div class="right-group">
<el-icon @click="handleShare"><Share /></el-icon>
<el-icon @click="handleFavorite"><Star /></el-icon>
<el-avatar size="medium" src="用户头像" @click="handleUserCenter" />
</div>
</div>
</header>
<!-- 2. 对话内容区 -->
<div class="chat-container">
<section class="doubao-chat" ref="chatContainer">
<div class="chat-inner">
<!-- 对话历史列表 -->
<div v-for="(msg, index) in chatHistory" :key="index" class="message-container">
<!-- 用户消息 - 居右对齐 -->
<div v-if="msg.isUser" class="user-message">
<div class="message-bubble">
<div class="message-content">
<p class="message-text">{{ msg.content }}</p>
<el-avatar src="用户头像" class="avatar-fixed" />
</div>
</div>
</div>
<!-- AI消息 - 居左对齐 -->
<div v-else class="ai-message">
<div class="message-bubble">
<div class="message-content">
<el-avatar src="/doubao.png" class="avatar-fixed" />
<p class="message-text">{{ msg.content }}</p>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- 3. 输入对话框 -->
<footer class="doubao-input">
<div class="input-inner">
<el-input v-model="prompt" placeholder="请输入内容..." @keyup.enter="sendMessage" />
<el-button type="primary" @click="sendMessage">发送</el-button>
</div>
</footer>
</div>
</template>
2. 核心逻辑(Script)
脚本部分使用 Vue 3 组合式 API,实现了数据管理、事件处理、流式响应等核心功能,关键代码解析如下:
响应式数据与状态管理
import { ref, nextTick, onMounted, watch, computed } from 'vue'
import llmApi from '@/api/llmApi'
import { ElMessage } from 'element-plus'
// 输入框内容
const prompt = ref('')
// 对话历史(区分用户/AI消息)
const chatHistory = ref([
{ isUser: false, content: "你好!我是智能助手小爱,有什么可以帮你的?" }
])
// 当前AI流式响应内容
const currentAIResponse = ref('')
// 加载状态
const isLoading = ref(false)
// SSE连接实例
let eventSource = ref(null)
// 会话ID(用于关联历史记录)
const memoryId = ref('10010')
动态标题生成(计算属性)
通过计算属性根据第一条用户消息自动生成对话标题,提升用户对会话的辨识度:
// 计算属性:根据聊天历史动态生成标题
const chatTitle = computed(() => {
if (chatHistory.value.length > 1) {
// 找到第一条用户消息
const firstUserMessage = chatHistory.value.find(msg => msg.isUser);
return firstUserMessage?.content || '新对话';
} else {
return '新对话';
}
});
核心功能:流式对话(SSE 实现)
使用 SSE(Server-Sent Events)实现 AI 回复的实时流式展示,这是提升交互体验的关键:
const sendMessage = () => {
if (!prompt.value.trim() || isLoading.value) {
ElMessage({ type: 'warning', message: '输入不能为空!' })
return
}
// 1. 添加用户消息到历史记录
const userMessage = prompt.value
chatHistory.value.push({ isUser: true, content: userMessage })
// 2. 初始化状态
isLoading.value = true
currentAIResponse.value = ''
prompt.value = '' // 清空输入框
// 3. 建立SSE连接,获取流式响应
const sseUrl = `http://localhost:9000/chatstream?memoryId=${memoryId.value}&prompt=${encodeURIComponent(userMessage)}`
eventSource.value = new EventSource(sseUrl)
// 4. 处理流式消息
eventSource.value.onmessage = (event) => {
// 累加AI响应内容
currentAIResponse.value += event.data
// 更新对话历史(实时展示)
if (chatHistory.value.length > 0 && !chatHistory.value[chatHistory.value.length - 1].isUser) {
// 更新最后一条AI消息
chatHistory.value[chatHistory.value.length - 1].content = currentAIResponse.value
} else {
// 添加新的AI消息
chatHistory.value.push({ isUser: false, content: currentAIResponse.value })
}
// 滚动到底部(确保最新消息可见)
nextTick(() => scrollToBottom())
}
// 5. 处理连接关闭
eventSource.value.onclose = () => {
isLoading.value = false
eventSource.value = null
}
// 6. 处理连接错误
eventSource.value.onerror = (error) => {
console.error('SSE 连接错误:', error)
chatHistory.value.push({ isUser: false, content: '[连接中断,请重试]' })
isLoading.value = false
eventSource.value.close()
}
}
辅助功能:分享与收藏
// 分享功能(复制当前对话链接到剪贴板)
const handleShare = async () => {
try {
const currentUrl = window.location.href;
await navigator.clipboard.writeText(currentUrl);
ElMessage({ type: 'success', message: '分享链接复制成功' });
} catch (error) {
// 兼容旧浏览器的备用方案
fallbackCopyTextToClipboard(currentUrl);
}
};
// 收藏功能(处理浏览器兼容性)
const handleFavorite = () => {
try {
if (window.bookmark) {
window.bookmark();
ElMessage({ type: 'success', message: '已添加到收藏夹' });
} else {
// 主流浏览器引导(Ctrl+D/Command+D)
ElMessage({
type: 'info',
message: '请按 Ctrl+D (Windows) 或 Command+D (Mac) 收藏当前页面'
});
}
} catch (error) {
ElMessage({ type: 'error', message: '无法自动收藏,请手动操作' });
}
};
历史记录加载与滚动优化
// 滚动到最新消息(确保DOM更新后执行)
const scrollToBottom = () => {
if (chatContainer.value) {
chatContainer.value.scrollTop = chatContainer.value.scrollHeight
}
}
// 监听对话历史变化,自动滚动到底部
watch(chatHistory, () => {
nextTick(scrollToBottom) // 关键:等待DOM更新后再滚动
}, { deep: true })
// 组件挂载时加载历史记录
onMounted(async () => {
// 从URL中获取会话ID(如/chat/10010)
const pathname = window.location.pathname;
const parts = pathname.split('/');
if (parts.length === 3 && parts[2].trim() !== '') {
memoryId.value = parts[2].trim();
// 加载历史记录
try {
isLoading.value = true;
const res = await llmApi.chatMemory(memoryId.value);
if (res.code === 200 && res.data.length > 0) {
chatHistory.value = res.data;
}
} catch (error) {
console.error('加载历史记录失败:', error);
} finally {
isLoading.value = false;
}
}
});
3. 样式设计(Style)
样式部分采用 Flex 布局实现响应式设计,区分用户与 AI 消息的视觉样式,关键代码如下:
/* 整体布局 */
.doubao-layout {
display: flex;
flex-direction: column;
height: calc(100vh - 16px);
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
/* 聊天内容区 */
.doubao-chat {
width: 100%;
flex: 1;
padding: 16px;
overflow-y: auto;
background-color: #f9fafb;
}
/* 消息容器(区分用户/AI) */
.user-message {
display: flex;
justify-content: flex-end; /* 用户消息居右 */
margin-right: 10px;
}
.ai-message {
display: flex;
justify-content: flex-start; /* AI消息居左 */
margin-left: 10px;
}
/* 消息气泡样式 */
.message-bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 20px;
display: flex;
align-items: center;
}
/* 响应式适配(小屏幕优化) */
@media (max-width: 768px) {
.chat-bubble {
width: 100%;
max-width: 90%;
}
}
技术亮点与实现思路
-
流式响应(SSE) :相比传统的一次性 HTTP 响应,SSE 允许服务器持续推送数据,实现 AI 回复的 "实时打字" 效果,大幅提升交互体验。关键是通过
EventSource
建立连接,监听onmessage
事件累加响应内容。 -
DOM 更新与滚动同步 :由于 Vue 的响应式更新是异步的,需要使用
nextTick
确保 DOM 更新后再执行滚动操作,否则会出现滚动位置不准确的问题。 -
历史记录管理 :通过会话 ID(
memoryId
)关联后端存储,在组件挂载时加载对应历史记录,实现会话的持久化。 -
用户体验细节:
- 输入为空时的友好提示
- 加载状态的视觉反馈
- 分享 / 收藏功能的浏览器兼容性处理
- 动态标题提升会话辨识度
遇到的问题与解决方案
-
SSE 连接复用问题 :多次发送消息可能导致旧连接未关闭,解决方案是在每次发送前检查并关闭已有
eventSource
。 -
滚动位置不准确 :由于 AI 响应是流式更新,需要在每次内容变化时重新计算滚动位置,通过
watch
监听chatHistory
变化触发滚动。 -
跨域问题 :SSE 请求可能遇到跨域限制,需要后端配置
Access-Control-Allow-Origin
等响应头。 -
输入框焦点管理 :发送消息后应自动聚焦输入框,可在
sendMessage
方法最后添加inputRef.value.focus()
。
总结
本项目基于 Vue 3 的组合式 API 实现了一个功能完整的智能聊天界面,核心亮点是通过 SSE 实现的流式响应机制,以及对用户体验细节的优化。通过本文的解析,你可以学习到:
- Vue 3 组合式 API 在实际项目中的应用
- SSE 技术实现实时流式响应的方法
- 对话界面的布局设计与响应式适配
- 提升用户体验的交互细节处理
后续可扩展的功能包括:消息编辑 / 删除、会话分类管理、富文本支持、暗黑模式等。希望本文能为你的聊天界面开发提供参考!