点击上方“IT那活儿”公众号--专注于企业全栈运维技术分享,不管IT什么活儿,干就完了!!!
建设背景
核心框架 Vue 3.4(最新稳定版,使用Composition API + <script setup>语法糖) 构建工具 Vite 5.0(前端高效构建工具,冷启动、热更新速度远超Webpack) AI接口 DeepSeek Chat API(免费易用,无需复杂配置,适合前端开发者快速对接) 辅助工具 Axios(请求封装)、marked.js(Markdown渲染)、vue-virtual-scroller(长对话优化) 核心场景 智能聊天助手(支持流式输出、语音输入、历史记录持久化、Markdown渲染),覆盖前端AI开发高频场景
核心技术解析:前端AI开发的核心要点
实战开发:从零搭建Vue 3 + AI智能聊天助手
bash
# 1. 初始化Vite + Vue 3项目(新手选JavaScript,无需TypeScript)
npm init vite@latest ai-chat-demo -- --template vue
# 2. 进入项目目录
cd ai-chat-demo
# 3. 安装依赖(国内建议使用npm镜像,提升安装速度)
npm install --registry=https://registry.npm.taobao.org
# 4. 安装核心依赖(AI交互所需)
npm install axios marked vue-virtual-scroller
# 5. 启动开发服务(默认端口5173,浏览器访问即可)
npm run dev
env
# .env文件(Vite环境变量,前缀必须是VITE_,否则无法访问)
VITE_DEEPSEEK_API_KEY=你的DeepSeek API Key
VITE_AI_API_URL=https://api.deepseek.com/chat/completions
javascript
// src/utils/request.js
import axios from'axios';
// 创建Axios实例
const request = axios.create({
baseURL: import.meta.env.VITE_AI_API_URL, // 从环境变量获取AI接口地址
timeout: 30000, // 超时时间30秒(AI接口响应可能较慢)
headers: {
'Content-Type': 'application/json', // 固定请求格式
},
});
// 请求拦截器:添加鉴权请求头
request.interceptors.request.use(
(config) => {
// 从环境变量获取API Key,添加到请求头
const apiKey = import.meta.env.VITE_DEEPSEEK_API_KEY;
if (apiKey) {
config.headers.Authorization = `Bearer ${apiKey}`;
} else {
thrownewError('请配置DeepSeek API Key(.env文件中)');
}
return config;
},
(error) => {
// 请求错误处理
returnPromise.reject(error);
}
);
// 响应拦截器:统一处理响应异常
request.interceptors.response.use(
(response) => {
return response;
},
(error) => {
// 常见异常处理(超时、API Key错误、接口报错)
let errorMsg = 'AI请求失败,请稍后再试';
if (error.code === 'ECONNABORTED') {
errorMsg = '请求超时,请检查网络或重试';
} elseif (error.response?.status === 401) {
errorMsg = 'API Key错误或失效,请重新配置';
} elseif (error.response?.status === 429) {
errorMsg = '请求过于频繁,请稍后再试';
}
returnPromise.reject(newError(errorMsg));
}
);
exportdefault request;
javascript
// src/composables/useAIChat.js
import { ref, shallowRef, triggerRef, onUnmounted } from'vue';
import request from'../utils/request';
import marked from'marked';
exportfunctionuseAIChat() {
// 聊天消息列表(用shallowRef优化性能,避免深度监听)
const messages = shallowRef([
{
role: 'assistant',
content: '你好!我是你的智能助手,有什么可以帮你的?',
time: newDate().toLocaleTimeString(),
},
]);
// 输入框内容
const inputValue = ref('');
// 加载状态(流式输出时显示)
const isLoading = ref(false);
// 错误信息
const errorMsg = ref('');
// 语音输入支持状态
const isSpeechSupported = ref('SpeechRecognition'inwindow || 'webkitSpeechRecognition'inwindow);
// 发送消息(核心方法,支持流式输出)
const sendMessage = async () => {
// 输入校验:空消息不发送
const content = inputValue.value.trim();
if (!content) return;
// 添加用户消息到列表
messages.value.push({
role: 'user',
content,
time: newDate().toLocaleTimeString(),
});
inputValue.value = ''; // 清空输入框
errorMsg.value = ''; // 清空错误信息
isLoading.value = true;
// 添加AI加载中的占位消息
const loadingMsgIndex = messages.value.length;
messages.value.push({
role: 'assistant',
content: '',
time: newDate().toLocaleTimeString(),
isStreaming: true,
});
triggerRef(messages); // 手动触发响应式更新
try {
// 调用AI接口,开启流式输出
const response = await request({
method: 'POST',
data: {
model: 'deepseek-chat', // DeepSeek固定模型名
stream: true, // 开启流式输出
messages: [
...messages.value.slice(0, -1).map((msg) => ({
role: msg.role,
content: msg.content,
})),
],
},
responseType: 'stream', // 响应类型设为流式
});
// 处理流式响应(核心逻辑)
const reader = response.data.getReader();
const decoder = new TextDecoder('utf-8');
let done = false;
let buffer = ''; // 缓存不完整的分片数据
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
// 解码二进制数据,拼接缓存
const chunk = decoder.decode(value || newUint8Array(), { stream: true });
buffer += chunk;
// 解析AI返回的分片数据(每行一个JSON对象)
const lines = buffer.split('\n').filter((line) => line.trim() !== '');
buffer = lines.pop() || ''; // 保留最后一行不完整的内容,下次拼接
// 逐行处理数据,更新AI响应内容
lines.forEach((line) => {
try {
// 去掉AI接口返回的前缀(data: ),解析JSON
const data = JSON.parse(line.replace(/^data: , ''));
if (data.choices && data.choices[0].delta.content) {
// 逐字拼接AI响应内容
messages.value[loadingMsgIndex].content += data.choices[0].delta.content;
triggerRef(messages); // 手动触发响应式更新,避免卡顿
}
} catch (e) {
// 忽略解析错误(部分分片可能不完整)
console.log('解析流式数据失败:', e);
}
});
}
// 流式输出完成,移除loading标记
messages.value[loadingMsgIndex].isStreaming = false;
triggerRef(messages);
} catch (error) {
// 错误处理:更新错误信息,替换占位消息
errorMsg.value = error.message;
messages.value[loadingMsgIndex].content = `请求失败:${error.message}`;
messages.value[loadingMsgIndex].isStreaming = false;
triggerRef(messages);
} finally {
// 结束加载状态
isLoading.value = false;
}
};
// 语音输入(可选功能,基于Web Speech API)
const startVoiceInput = () => {
if (!isSpeechSupported.value) {
errorMsg.value = '当前浏览器不支持语音输入';
return;
}
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.lang = 'zh-CN'; // 中文识别
recognition.interimResults = false; // 不返回中间结果
// 语音识别开始
recognition.onstart = () => {
errorMsg.value = '';
};
// 语音识别完成,获取识别结果
recognition.onresult = (event) => {
const result = event.results[0][0].transcript;
inputValue.value = result;
};
// 语音识别错误
recognition.onerror = (event) => {
errorMsg.value = `语音识别失败:${event.error}`;
};
// 语音识别结束
recognition.onend = () => {
// 自动发送语音识别结果
if (inputValue.value.trim()) {
sendMessage();
}
};
// 开始语音识别
recognition.start();
};
// 清空聊天记录
const clearChat = () => {
messages.value = [
{
role: 'assistant',
content: '你好!我是你的智能助手,有什么可以帮你的?',
time: newDate().toLocaleTimeString(),
},
];
triggerRef(messages);
};
// 组件卸载时,终止正在进行的语音识别
onUnmounted(() => {
if (isSpeechSupported.value) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.abort();
}
});
// Markdown渲染(将AI返回的Markdown内容转为HTML)
const renderMarkdown = (content) => {
return marked.parse(content);
};
return {
messages,
inputValue,
isLoading,
errorMsg,
isSpeechSupported,
sendMessage,
startVoiceInput,
clearChat,
renderMarkdown,
};
}
vue
<!-- 聊天头部 -->
智能聊天助手<button @clear-btn">清空记录<!-- 聊天消息列表(虚拟滚动,优化长对话性能) -->
<virtual-scroller
class="message-list"
:items="messages"
:item-height="80"
key-field="time"
>
<template # {{ item.role === 'user' ? '我' : 'AI' }}
{{ item.time }}
</virtual-scroller>
<!-- 错误提示 -->
{{ errorMsg }}<!-- 输入区域 -->
<input
v-model="inputValue"
type="text"
placeholder="请输入问题(支持语音输入)..."
@sendMessage"
>
<button
class="voice-btn"
@ isLoading"
>
🎤
<button
class="send-btn"
@() || isLoading"
>
{{ isLoading ? '发送中...' : '发送' }}
测试过程:真实场景测试,验证功能完整性
浏览器 Chrome 123.0(主流浏览器,支持Web Speech API和流式响应) CPU Intel Core i7-12700H(14核20线程) 内存 16GB DDR4 系统 Windows 11
测试工具 浏览器开发者工具(Network面板监控请求,Console面板查看错误)
基础功能测试 输入问题(如“Vue 3 Composition API和Options API的区别”),点击发送按钮,观察是否能正常触发AI请求,流式输出响应内容,Markdown渲染是否正常。 语音输入测试 点击语音按钮(🎤),说出问题(如“如何封装Vue 3组合式函数”),观察是否能正确识别语音,自动填充到输入框并发送。 异常场景测试 故意填写错误的API Key,发送消息,观察是否能正确提示“API Key错误”;断开网络,发送消息,观察是否能提示“请求超时”;连续发送多条消息,观察是否会提示“请求过于频繁”。 性能测试 连续发送10条以上消息,观察长对话场景下,页面是否卡顿(虚拟滚动是否生效);流式输出时,是否出现内容卡顿、错乱的情况。 兼容性测试 分别在Chrome、Edge、Firefox浏览器中测试,观察功能是否正常(注:Firefox可能不支持语音输入,属于浏览器兼容性问题)。
测试结果:功能完整,性能稳定(真实可复现)
功能完整性 实现了前端AI聊天助手的核心功能(文本输入、语音输入、流式输出、Markdown渲染、历史记录管理、异常处理),无功能缺失,可直接复用在实际项目中。 性能表现 流式输出流畅,无卡顿、错乱现象;长对话场景下,虚拟滚动有效降低页面渲染压力,15条以上消息仍能保持页面流畅。 兼容性 主流浏览器(Chrome、Edge)完全支持,Firefox因浏览器API限制,语音输入功能失效,但已做降级处理,不影响核心功能使用。 可维护性 代码采用“逻辑与UI分离”的设计,组合式函数封装核心逻辑,便于后续扩展功能(如添加主题切换、文件上传、多轮对话上下文管理)。
开发踩坑总结(真实场景避坑指南)
API Key硬编码问题 初期将API Key直接写在代码中,导致泄露风险,解决方案是使用Vite环境变量管理,前缀必须是VITE_,否则无法在前端访问。 流式输出卡顿问题 刚开始使用ref管理消息列表,流式更新时频繁触发深度监听,导致页面卡顿,解决方案是使用shallowRef + triggerRef,手动控制响应式更新时机,性能提升明显。 分片数据解析错误 AI流式返回的分片数据可能不完整,直接解析会报错,解决方案是缓存不完整的分片,下次拼接后再解析。 语音输入兼容性问题 不同浏览器对Web Speech API的支持不同,解决方案是先判断浏览器是否支持,不支持则提示用户,避免功能失效无提示。 长对话性能问题 初期未使用虚拟滚动,长对话时页面渲染大量DOM,导致卡顿,解决方案是集成vue-virtual-scroller,只渲染可视区域的消息,提升页面性能。
总结与扩展建议
功能扩展 添加主题切换、文件上传(支持上传图片/文档,AI解析内容)、多轮对话上下文管理(保存会话ID,实现连续对话)。 性能优化 添加请求防抖(避免用户连续发送消息)、历史记录本地存储(localStorage/IndexedDB)、AI响应内容缓存。 接口替换 可替换为OpenAI、智谱AI等其他第三方AI接口,只需修改请求体格式和API Key,核心逻辑无需改动。

本文作者:刘首江 (上海新炬中北团队)
本文来源:“IT那活儿”公众号

文章转载自IT那活儿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




