深入解析Vue 3聊天系统的技术实现

发布于:2025-07-13 ⋅ 阅读:(16) ⋅ 点赞:(0)

 引言

        随着大语言模型技术的发展,基于AI的聊天问答系统成为了当前技术热点。本文将深入分析一个基于Vue 3的大模型聊天系统的技术实现,该项目集成了SSE流式响应、WebSocket语音识别、Markdown渲染、联网搜索等多种现代前端技术。

 一、项目架构与技术栈

1.1 项目整体架构

采用了monorepo架构,使用pnpm + Turborepo管理多包项目,实现PC端和H5端代码共享:

```

xxx/

├── apps/                # 应用目录

│   ├── pc/              # PC端应用

│   └── h5/              # H5端应用

├── packages/            # 共享包目录

│   ├── api/             # API接口

│   ├── components/      # 共享组件

│   ├── stores/          # 状态管理

│   └── utils/           # 工具函数

├── turbo.json           # Turborepo配置

└── pnpm-workspace.yaml  # pnpm工作区配置

1.2 技术栈选型

项目采用了以下核心技术栈:

1.  框架选型:Vue 3 + Composition API

2.  状态管理:Pinia (Composition API风格)

3.  UI框架:

    -   PC端:Element Plus

    -   H5端:Vant

4.  构建工具:Vite

5.  包管理:pnpm + Turborepo

6.  TypeScript:全面类型支持

7.  网络请求:Axios + 自定义请求封装

8.  特色技术:

    -   SSE流式响应:fetchEventSource + TransformStream

    -   WebSocket语音识别:MediaRecorder API + WebSocket

    -   Markdown渲染:markdown-it + 自定义插件

    -   代码高亮:Shiki

1.3 技术选型理由

1.  Vue 3 + Composition API:提供更好的代码组织和复用能力,适合复杂业务逻辑

2.  Pinia:轻量级状态管理,TypeScript支持良好,适合Vue 3项目

3.  Monorepo架构:便于多端代码共享和统一版本管理

4.  Element Plus/Vant:分别适配PC端和移动端,提供丰富的组件库

5.  Vite:提供快速的开发体验和高效的构建过程

二、SSE流式响应与打字机效果实现

2.1 SSE流式响应原理

使用Server-Sent Events (SSE)技术实现大模型回复的流式响应,通过fetchEventSource库处理事件流:

```typescript

// 使用fetchEventSource库处理SSE连接

const { body } = await fetchEventSource(url, {

  method: 'POST',

  headers,

  body: JSON.stringify(payload),

  async onopen(response) {

    if (response.ok) {

      return; // 连接成功

    }

    throw new Error(`Failed to open SSE stream: ${response.status}`);

  },

  onmessage(msg) {

    // 处理SSE消息

    if (msg.data === '[DONE]') {

      messageStore.updateMessageStatus(messageId, 'complete');

      return;

    }

    try {

      const data = JSON.parse(msg.data);

      // 处理流式响应数据

      messageStore.appendMessageContent(messageId, data.content);

    } catch (e) {

      console.error('解析SSE数据失败', e);

    }

  },

  onerror(err) {

    console.error('SSE错误', err);

    messageStore.updateMessageStatus(messageId, 'error', err.message);

  }

});

```

2.2 自定义TransformStream处理

项目实现了自定义TransformStream处理二进制流数据,将其转换为结构化事件:

```typescript

// 自定义TransformStream处理二进制流

const transformStream = new TransformStream({

  transform(chunk, controller) {

    const decoder = new TextDecoder();

    const text = decoder.decode(chunk);

    const lines = text.split('\n\n').filter(Boolean);

   

    lines.forEach(line => {

      if (line.startsWith('data:')) {

        const data = line.slice(5).trim();

        if (data === '[DONE]') {

          controller.enqueue({ done: true });

        } else {

          try {

            const parsed = JSON.parse(data);

            controller.enqueue(parsed);

          } catch (e) {

            console.error('解析SSE数据失败', e);

          }

        }

      }

    });

  }

});

```

2.3 打字机效果实现

打字机效果通过增量更新内容和CSS动画实现:

```typescript

// 状态管理中的增量更新

const appendMessageContent = (messageId, content) => {

  const index = messages.value.findIndex(msg => msg.id === messageId);

  if (index !== -1) {

    // 增量更新内容

    messages.value[index].content += content;

    // 触发滚动到底部

    nextTick(() => {

      scrollToBottom();

    });

  }

};

```

```css

/* 打字机效果CSS */

.typing-effect {

  display: inline-block;

  border-right: 2px solid #000;

  animation: blink-caret 0.75s step-end infinite;

  white-space: pre-wrap;

}

@keyframes blink-caret {

  from, to { border-color: transparent }

  50% { border-color: #000 }

}

```

2.4 技术难点与解决方案

1.  二进制流解析:

    -   难点:处理二进制流数据并解析为结构化事件

    -   解决方案:使用TextDecoder和自定义TransformStream

2.  增量更新DOM:

    -   难点:频繁DOM更新可能导致性能问题

    -   解决方案:使用Vue的响应式系统和nextTick优化渲染

3.  错误处理与重连:

    -   难点:网络波动导致连接中断

    -   解决方案:实现重试机制和错误状态管理

三、WebSocket语音识别集成

 3.1 语音识别架构

使用WebSocket和MediaRecorder API实现实时语音识别:

```

语音识别流程:

 获取麦克风权限 => 建立WebSocket连接 => 初始化录音设备 => 发送会话参数 => 开始录音

=> 显示录音UI状态 => 显采集音频数据 => Base64编码 => WebSocket发送数据=> 解析识别结果

=> 更新输入框文本

```

3.2 WebSocket连接与音频处理

```typescript

// 建立WebSocket连接

const createWebSocket = () => {

  ws = new WebSocket(wsUrl);

  ws.onopen = () => {

    console.log('WebSocket连接已建立');

    wsStatus.value = 'open';

    startRecording(); // 开始录音

  };

  ws.onmessage = (e) => {

    try {

      const response = JSON.parse(e.data);

      // 处理WPGS动态文本修正

      if (response.code === 0) {

        const result = response.data.result;

        const isEnd = response.data.status === 2;

       

        // 处理增量更新和文本替换

        if (response.data.voice_text_str) {

          const latestText = response.data.voice_text_str;

          // 替换或追加文本

          voiceText.value = latestText;

        }

       

        if (isEnd) {

          stopRecording();

        }

      }

    } catch (error) {

      console.error('处理WebSocket消息失败', error);

    }

  };

  ws.onerror = (e) => {

    console.error('WebSocket错误', e);

    wsStatus.value = 'error';

    handleReconnect();

  };

  ws.onclose = () => {

    console.log('WebSocket连接已关闭');

    wsStatus.value = 'closed';

  };

};

```

3.3 MediaRecorder实现录音

```typescript

// 使用MediaRecorder API录制音频

const startRecording = async () => {

  try {

    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });

    mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });

   

    mediaRecorder.ondataavailable = (e) => {

      if (e.data.size > 0 && ws && ws.readyState === WebSocket.OPEN) {

        ws.send(e.data); // 发送音频数据到WebSocket

      }

    };

   

    // 设置录音间隔,每100ms发送一次数据

    mediaRecorder.start(100);

    isRecording.value = true;

  } catch (error) {

    console.error('开始录音失败', error);

    toastManager.show('无法访问麦克风');

  }

};

// 停止录音

const stopRecording = () => {

  if (mediaRecorder && mediaRecorder.state !== 'inactive') {

    mediaRecorder.stop();

    // 停止所有音频轨道

    mediaRecorder.stream.getTracks().forEach(track => track.stop());

    isRecording.value = false;

  }

  if (ws && ws.readyState === WebSocket.OPEN) {

    ws.close();

  }

};

```

3.4 WPGS技术实现动态文本修正

使用WPGS (Word Piecewise Group Search) 技术实现语音识别过程中的动态文本修正:

```typescript

// 处理WPGS返回的动态修正文本

const handleWpgsResult = (result) => {

  if (!result || !result.ws) return '';

  let text = '';

  // 处理每个词组

  result.ws.forEach(ws => {

    if (ws.cw && ws.cw.length > 0) {

      // 取每个词组中置信度最高的结果

      const bestResult = ws.cw.reduce((prev, current) =>

        (current.sc > prev.sc) ? current : prev, ws.cw[0]);

      text += bestResult.w;

    }

  });

  return text;

};

```

3.5 技术难点与解决方案

1.  浏览器兼容性:

    -   难点:不同浏览器对MediaRecorder的支持不同

    -   解决方案:实现兼容性检测和降级处理

2.  WebSocket连接稳定性:

    -   难点:网络波动导致连接中断

    -   解决方案:实现重连机制和状态恢复

3.  WPGS动态文本修正:

    -   难点:处理服务器返回的增量更新和文本替换

    -   解决方案:设计专门的文本处理逻辑,处理不同类型的更新

四、Markdown渲染与代码高亮实现

4.1 Mrkdown渲染核心

使用markdown-it库实现Markdown渲染,并通过自定义插件扩展功能:

```typescript

// Markdown渲染配置

const md = markdownit({

  html: true,

  linkify: true,

  typographer: true,

  highlight: (code, lang) => {

    // 使用Shiki进行代码高亮

    try {

      return shiki.codeToHtml(code, { lang, theme: 'github-dark' });

    } catch (e) {

      console.error('代码高亮失败', e);

      return `<pre><code>${escape(code)}</code></pre>`;

    }

  }

});

// 注册自定义插件

md.use(markdownItAnchor)

  .use(markdownItToc)

  .use(customPlugin); // 处理项目特有的语法

```

4.2 Shiki代码高亮集成

使用Shiki实现高质量的代码语法高亮:

```typescript

// 初始化Shiki高亮器

import { getHighlighter } from 'shiki';

const initHighlighter = async () => {

  try {

    highlighter = await getHighlighter({

      theme: 'github-dark',

      langs: ['javascript', 'typescript', 'html', 'css', 'json', 'python', 'java']

    });

  } catch (e) {

    console.error('初始化代码高亮失败', e);

  }

};

// 代码高亮函数

const highlightCode = (code, lang) => {

  if (!highlighter) return escape(code);

  try {

    // 如果语言不支持,使用纯文本

    const language = highlighter.getLoadedLanguages().includes(lang) ? lang : 'text';

    return highlighter.codeToHtml(code, { lang: language });

  } catch (e) {

    console.error('代码高亮失败', e);

    return escape(code);

  }

};

```

4.3 自定义Markdown组件

实现了自定义Markdown渲染组件,支持复制代码、行号显示等功能:

```vue

<!-- Markdown渲染组件 -->

<template>

  <div class="markdown-container" ref="markdownRef">

    <div v-html="renderedContent" @click="handleClick"></div>

  </div>

</template>

<script setup>

import { ref, computed, onMounted } from 'vue';

import markdownIt from 'markdown-it';

import { useShiki } from '@/hooks/useShiki';

const props = defineProps({

  content: {

    type: String,

    default: ''

  }

});

const markdownRef = ref(null);

const { highlight } = useShiki();

// 配置markdown-it

const md = markdownIt({

  html: true,

  linkify: true,

  typographer: true,

  highlight

});

// 渲染内容

const renderedContent = computed(() => {

  return md.render(props.content || '');

});

// 处理点击事件,如复制代码

const handleClick = (e) => {

  const target = e.target;

  // 处理复制代码按钮点击

  if (target.classList.contains('copy-code-button')) {

    const codeBlock = target.closest('.code-block');

    const code = codeBlock.querySelector('code').textContent;

   

    navigator.clipboard.writeText(code)

      .then(() => {

        target.textContent = '已复制';

        setTimeout(() => {

          target.textContent = '复制';

        }, 2000);

      })

      .catch(err => {

        console.error('复制失败', err);

      });

  }

};

// 添加复制按钮到代码块

onMounted(() => {

  if (!markdownRef.value) return;

  const codeBlocks = markdownRef.value.querySelectorAll('pre code');

  codeBlocks.forEach((codeBlock) => {

    const pre = codeBlock.parentElement;

    pre.classList.add('code-block');

   

    // 创建复制按钮

    const copyButton = document.createElement('button');

    copyButton.className = 'copy-code-button';

    copyButton.textContent = '复制';

   

    pre.appendChild(copyButton);

  });

});

</script>

```

4.4 XSS防护措施

实现了Markdown渲染的XSS防护:

```typescript

// XSS防护配置

const safeMarkdown = markdownit({

  html: false, // 禁用HTML标签

  linkify: true,

  typographer: true

});

// 自定义规则过滤器

const customRender = (tokens, idx, options, env, self) => {

  const token = tokens[idx];

  // 过滤潜在危险属性

  if (token.attrs) {

    token.attrs = token.attrs.filter(([key]) => {

      return !key.startsWith('on') && key !== 'style';

    });

  }

  // 使用原始渲染器

  return self.renderToken(tokens, idx, options);

};

// 替换默认渲染器

Object.keys(safeMarkdown.renderer.rules)

  .forEach(key => {

    const originalRule = safeMarkdown.renderer.rules[key];

    safeMarkdown.renderer.rules[key] = (tokens, idx, options, env, self) => {

      return customRender(tokens, idx, options, env, originalRule || self.renderToken);

    };

  });

```

4.5 技术难点与解决方案

1.  复杂Markdown渲染性能:

    -   难点:大量Markdown内容渲染可能导致性能问题

    -   解决方案:使用虚拟滚动和延迟渲染

2.  代码高亮主题适配:

    -   难点:适配深色/浅色主题切换

    -   解决方案:动态加载主题并缓存渲染结果

3.  XSS安全防护:

    -   难点:防止Markdown中的恶意代码执行

    -   解决方案:禁用HTML标签和过滤危险属性

五、联网搜索与知识库集成

5.1 联网搜索实现

实现了联网搜索功能,支持实时获取搜索结果:

```typescript

// 联网搜索API调用

const searchWeb = async (query) => {

  searchState.value = 'loading';

  try {

    const { data } = await webSearch({

      query,

      limit: 5

    });

   

    if (data.code === 200) {

      searchResults.value = data.data.map(item => ({

        id: item.id,

        title: item.title,

        content: item.snippet,

        url: item.url,

        source: 'web'

      }));

      searchState.value = 'success';

    } else {

      searchState.value = 'error';

      searchError.value = data.message || '搜索失败';

    }

  } catch (error) {

    console.error('联网搜索失败', error);

    searchState.value = 'error';

    searchError.value = error.message || '网络错误';

  }

};

```

5.2 知识库集成

实现知识库集成功能,支持知识引用和来源追溯:

```typescript

// 知识库搜索

const searchKnowledgeBase = async (query) => {

  kbState.value = 'loading';

  try {

    const { data } = await knowledgeSearch({

      query,

      limit: 10

    });

   

    if (data.code === 200) {

      kbResults.value = data.data.map(item => ({

        id: item.id,

        title: item.title,

        content: item.content,

        source: item.source,

        type: item.type,

        confidence: item.confidence

      }));

      kbState.value = 'success';

    } else {

      kbState.value = 'error';

      kbError.value = data.message || '搜索失败';

    }

  } catch (error) {

    console.error('知识库搜索失败', error);

    kbState.value = 'error';

    kbError.value = error.message || '网络错误';

  }

};

```

5.3 引用标注组件

实现引用标注组件,支持悬浮预览和来源跳转:

```vue

<!-- 引用标注组件 -->

<template>

  <div class="reference-container">

    <div

      v-for="(ref, index) in references"

      :key="index"

      class="reference-item"

      @mouseenter="showPreview(index)"

      @mouseleave="hidePreview"

    >

      <span class="reference-number">[{{ index + 1 }}]</span>

      <div v-if="activePreview === index" class="reference-preview">

        <div class="preview-title">{{ ref.title }}</div>

        <div class="preview-content">{{ ref.content }}</div>

        <div class="preview-source">

          <a :href="ref.url" target="_blank">{{ ref.source }}</a>

        </div>

      </div>

    </div>

  </div>

</template>

<script setup>

import { ref } from 'vue';

const props = defineProps({

  references: {

    type: Array,

    default: () => []

  }

});

const activePreview = ref(null);

const showPreview = (index) => {

  activePreview.value = index;

};

const hidePreview = () => {

  activePreview.value = null;

};

</script>

```

5.4 技术难点与解决方案

1.  搜索结果整合:

    -   难点:整合多种来源的搜索结果并保持一致的用户体验

    -   解决方案:设计统一的数据结构和展示组件

2.  引用标注实现:

    -   难点:实现引用标注和来源追溯功能

    -   解决方案:设计引用标注组件,支持悬浮预览和来源跳转

3.  搜索性能优化:

    -   难点:提高搜索响应速度和结果相关性

    -   解决方案:实现搜索缓存和结果排序优化

六、Pinia状态管理与消息处理

6.1 Composition API风格的Pinia实现

使用Composition API风格实现Pinia状态管理:

```typescript

// 消息状态管理

export const useMessageStore = defineStore('message', () => {

  // 状态

  const messages = ref([]);

  const currentSession = ref(null);

  const sessions = ref([]);

  const loading = ref(false);

  // Getters

  const currentMessages = computed(() => {

    if (!currentSession.value) return [];

    return messages.value.filter(msg => msg.sessionId === currentSession.value);

  });

  // Actions

  const addMessage = (message) => {

    messages.value.push({

      ...message,

      sessionId: currentSession.value,

      timestamp: Date.now()

    });

   

    // 保存到本地存储

    saveMessages();

  };

  const updateMessageStatus = (messageId, status, error = null) => {

    const index = messages.value.findIndex(msg => msg.id === messageId);

    if (index !== -1) {

      messages.value[index] = {

        ...messages.value[index],

        status,

        error

      };

     

      // 保存到本地存储

      saveMessages();

    }

  };

  const appendMessageContent = (messageId, content) => {

    const index = messages.value.findIndex(msg => msg.id === messageId);

    if (index !== -1) {

      messages.value[index].content += content;

     

      // 延迟保存,避免频繁写入

      debounce(saveMessages, 500)();

    }

  };

  // 保存消息到本地存储

  const saveMessages = () => {

    try {

      localStorage.setItem('chat_messages', JSON.stringify(messages.value));

    } catch (e) {

      console.error('保存消息失败', e);

    }

  };

  // 加载消息

  const loadMessages = () => {

    try {

      const savedMessages = localStorage.getItem('chat_messages');

      if (savedMessages) {

        messages.value = JSON.parse(savedMessages);

      }

    } catch (e) {

      console.error('加载消息失败', e);

    }

  };

  // 初始化加载

  loadMessages();

  return {

    messages,

    currentSession,

    sessions,

    loading,

    currentMessages,

    addMessage,

    updateMessageStatus,

    appendMessageContent

  };

});

```

6.2 消息处理流程

实现完整的消息处理流程:

```typescript

// 发送消息流程

const sendMessage = async (content) => {

  if (!content.trim()) return;

  const messageId = generateUUID();

  // 添加用户消息

  messageStore.addMessage({

    id: messageId,

    role: 'user',

    content,

    status: 'sending'

  });

  // 添加AI回复占位

  const aiMessageId = generateUUID();

  messageStore.addMessage({

    id: aiMessageId,

    role: 'assistant',

    content: '',

    status: 'waiting'

  });

  try {

    // 更新用户消息状态

    messageStore.updateMessageStatus(messageId, 'sent');

   

    // 更新AI消息状态

    messageStore.updateMessageStatus(aiMessageId, 'receiving');

   

    // 发送SSE请求

    await fetchChatCompletion(content, aiMessageId);

   

    // 请求完成后更新状态

    messageStore.updateMessageStatus(aiMessageId, 'complete');

  } catch (error) {

    console.error('发送消息失败', error);

    messageStore.updateMessageStatus(aiMessageId, 'error', error.message);

  }

};

```

6.3 会话管理

实现完整的会话管理功能:

```typescript

// 会话管理

const createSession = () => {

  const sessionId = generateUUID();

  sessions.value.push({

    id: sessionId,

    title: '新对话',

    createdAt: Date.now(),

    updatedAt: Date.now()

  });

  currentSession.value = sessionId;

  saveSessions();

  return sessionId;

};

const switchSession = (sessionId) => {

  currentSession.value = sessionId;

};

```

6.4 技术难点与解决方案

1.  状态持久化:

    -   难点:刷新页面后如何恢复会话状态

    -   解决方案:使用localStorage保存消息和会话数据

2.  状态同步:

    -   难点:在多个组件之间同步状态

    -   解决方案:使用Pinia集中管理状态,通过订阅实现同步

3.  性能优化:

    -   难点:大量消息导致状态管理性能下降

    -   解决方案:使用计算属性和防抖优化状态更新

七、文件上传与处理功能

7.1 队列管理与并发控制

使用队列管理控制并发上传数量:

```typescript

let queue = []; // 文件队列

let activeCount = 0; // 当前上传数量

const processQueue = () => {

  while (queue.length > 0 && activeCount < 5) {

    const file = queue.shift();

    if (file) {

      activeCount++;

      uploadFile(file).finally(() => {

        activeCount--;

        processQueue();

      });

    }

  }

};

```

7.2文件状态轮询

文件上传成功后,通过轮询检查服务器端解析状态:

```typescript

const pollStatus = async (docIds) => {

  try {

    const { data } = await uploadFileList({ docList: docIds });

    // ...更新文件列表状态...

    const needsPolling = fileList.value.some(file => file.status === 0 || file.status === -4);

    if (needsPolling) {

      setTimeout(() => pollStatus(docIds), 5000);

    }

  } catch (error) {

    console.error('获取文件状态失败:', error);

  }

};

```

7.3 技术难点与解决方案

1.  并发上传控制:

    -   难点:避免同时上传过多文件导致性能问题

    -   解决方案:使用队列管理机制限制并发数

2.  文件解析状态同步:

    -   难点:客户端需要及时获取服务器端的文件解析状态

    -   解决方案:实现轮询机制,定期检查文件状态

3.  多类型文件预览:

    -   难点:支持图片、PDF等多种文件格式的预览

    -   解决方案:根据文件类型动态选择预览组件

八、技术难点与亮点总结

8.1 核心技术难点

1.  SSE流式响应与打字机效果:处理二进制流、事件分离和渲染优化

2.  多端适配与代码共享:共享核心逻辑、适配UI差异

3.  WebSocket实时语音识别:处理浏览器兼容性、动态文本修正

4.  大量消息渲染性能优化:虚拟列表、DOM更新优化

5.  知识库集成与引用标注:整合多源数据、实现引用溯源

8.2 项目技术亮点

1.  自定义TransformStream处理SSE事件流:高效的二进制数据解析

2.  语音识别与文字输入的无缝集成:WPGS技术实现实时语音识别

3.  Markdown渲染与代码高亮的精细实现:自定义插件、Shiki高亮

4.  完善的错误处理和状态恢复机制:全局错误处理、重试机制

5.  高效的文件上传与处理系统:队列管理、状态轮询

九、面试准备:问题与答案

1. 如何处理SSE流式响应中的打字机效果?

使用了自定义的TransformStream处理SSE事件流,将二进制数据解析为结构化事件。为实现平滑的打字机效果,我们采用了以下策略:

1.  使用TextDecoder解析二进制数据

2.  分割事件流并解析JSON数据

3.  使用状态管理将解析后的内容增量更新到UI

4.  实现防抖和节流机制,优化DOM更新频率

5.  使用CSS动画增强打字机视觉效果

2. 如何实现多端代码共享?

采用monorepo架构(pnpm + Turborepo)实现代码共享:

1.  将项目分为apps/和packages/两个主要目录

2.  apps/目录下分别实现PC和H5端的UI层

3.  packages/目录存放共享逻辑,如API请求、状态管理、工具函数等

4.  使用Pinia实现平台无关的状态管理

5.  通过依赖注入方式解决平台特定功能

6.  使用条件编译和动态导入处理平台差异

3. 如何优化大量消息渲染的性能?

采用多种策略优化大量消息渲染的性能:

1.  实现虚拟列表,只渲染可视区域的消息

2.  使用Web Worker分离复杂计算逻辑,如Markdown解析

3.  实现消息分页和懒加载机制

4.  对Markdown渲染和代码高亮进行缓存

5.  使用防抖和节流优化频繁DOM更新

6.  实现高效的DOM更新策略,减少重排重绘

7.  使用requestAnimationFrame优化动画和滚动效果

4. 项目中最大的技术挑战是什么?如何解决的?

最大的技术挑战是实现流式响应下的高性能渲染和状态管理。我们面临以下挑战:

1.  需要处理大量实时更新的消息内容

2.  复杂Markdown和代码高亮渲染导致性能问题

3.  在流式响应过程中保持良好的滚动体验

4.  处理网络波动和错误恢复

解决方案:

1.  设计高效的状态管理架构,将消息内容和UI状态分离

2.  实现增量渲染策略,只更新变化的部分

3.  使用Web Worker分离复杂计算

4.  实现虚拟滚动和分页加载

5.  设计完善的错误处理和重试机制

十、结语

通过对聊天系统的深入分析,不仅掌握了Vue 3、Pinia、Monorepo等现代前端技术的实践应用,还深入了解了SSE流式响应、WebSocket语音识别、Markdown渲染优化等高级技术的实现细节。这些经验对于构建高性能、可扩展的现代Web应用具有重要的参考价值


网站公告

今日签到

点亮在社区的每一天
去签到