基于 Vue 3 的智能聊天界面实现:从 UI 到流式响应全解析

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

项目介绍

这个聊天界面模仿了主流 AI 助手的交互模式,具备以下特点:

  • 实时流式响应:通过 SSE(Server-Sent Events)实现 AI 回复的 "打字机" 效果
  • 完整的对话生命周期管理:支持新对话创建、历史记录加载与展示
  • 增强用户体验的辅助功能:对话分享、收藏、动态标题
  • 响应式设计:适配不同屏幕尺寸
  • 清晰的视觉层次:区分用户与 AI 消息,优化布局与交互

功能亮点

  1. 流式响应(SSE):无需等待完整回复,AI 边生成边展示,减少用户等待感
  2. 对话历史管理:自动存储对话记录,区分用户与 AI 消息
  3. 动态标题:根据第一条用户消息自动生成对话标题
  4. 实用辅助功能:支持对话分享(复制链接)、收藏(浏览器书签)
  5. 响应式布局:在手机与桌面设备上均有良好表现

核心代码解析

整体架构

界面采用经典的三部分布局:顶部导航区、中间聊天内容区、底部输入区。代码使用 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%;
  }
}

技术亮点与实现思路

  1. 流式响应(SSE) :相比传统的一次性 HTTP 响应,SSE 允许服务器持续推送数据,实现 AI 回复的 "实时打字" 效果,大幅提升交互体验。关键是通过EventSource建立连接,监听onmessage事件累加响应内容。

  2. DOM 更新与滚动同步 :由于 Vue 的响应式更新是异步的,需要使用nextTick确保 DOM 更新后再执行滚动操作,否则会出现滚动位置不准确的问题。

  3. 历史记录管理 :通过会话 ID(memoryId)关联后端存储,在组件挂载时加载对应历史记录,实现会话的持久化。

  4. 用户体验细节

    • 输入为空时的友好提示
    • 加载状态的视觉反馈
    • 分享 / 收藏功能的浏览器兼容性处理
    • 动态标题提升会话辨识度

遇到的问题与解决方案

  1. SSE 连接复用问题 :多次发送消息可能导致旧连接未关闭,解决方案是在每次发送前检查并关闭已有eventSource

  2. 滚动位置不准确 :由于 AI 响应是流式更新,需要在每次内容变化时重新计算滚动位置,通过watch监听chatHistory变化触发滚动。

  3. 跨域问题 :SSE 请求可能遇到跨域限制,需要后端配置Access-Control-Allow-Origin等响应头。

  4. 输入框焦点管理 :发送消息后应自动聚焦输入框,可在sendMessage方法最后添加inputRef.value.focus()

总结

本项目基于 Vue 3 的组合式 API 实现了一个功能完整的智能聊天界面,核心亮点是通过 SSE 实现的流式响应机制,以及对用户体验细节的优化。通过本文的解析,你可以学习到:

  • Vue 3 组合式 API 在实际项目中的应用
  • SSE 技术实现实时流式响应的方法
  • 对话界面的布局设计与响应式适配
  • 提升用户体验的交互细节处理

后续可扩展的功能包括:消息编辑 / 删除、会话分类管理、富文本支持、暗黑模式等。希望本文能为你的聊天界面开发提供参考!

相关推荐
知识分享小能手22 分钟前
Vue3 学习教程,从入门到精通,使用 VSCode 开发 Vue3 的详细指南(3)
前端·javascript·vue.js·学习·前端框架·vue·vue3
姑苏洛言40 分钟前
搭建一款结合传统黄历功能的日历小程序
前端·javascript·后端
你的人类朋友2 小时前
🤔什么时候用BFF架构?
前端·javascript·后端
知识分享小能手2 小时前
Bootstrap 5学习教程,从入门到精通,Bootstrap 5 表单验证语法知识点及案例代码(34)
前端·javascript·学习·typescript·bootstrap·html·css3
一只小灿灿2 小时前
前端计算机视觉:使用 OpenCV.js 在浏览器中实现图像处理
前端·opencv·计算机视觉
前端小趴菜053 小时前
react状态管理库 - zustand
前端·react.js·前端框架
Jerry Lau3 小时前
go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
前端·golang·gin
我命由我123453 小时前
前端开发问题:SyntaxError: “undefined“ is not valid JSON
开发语言·前端·javascript·vue.js·json·ecmascript·js
0wioiw04 小时前
Flutter基础(前端教程③-跳转)
前端·flutter