Vue3对接deepseek实现ai对话
1.第一步
申请deepseek API,详细教程参考下面视频:
DeepSeek API的申请方式_哔哩哔哩_bilibili
2.第二步
创建Vue3项目
【带小白做毕设】01. 前端Vue3 框架的快速搭建以及项目工程的讲解_哔哩哔哩_bilibili
3.第三步
创建deepseek.js封装deepseek请求接口
import axios from 'axios';const DEEPSEEK_API_URL = 'https://api.deepseek.com'; // DeepSeek API端点
const API_KEY = '你的密钥'; //DeepSeek API密钥const deepseekApi = axios.create({baseURL: DEEPSEEK_API_URL,headers: {'Authorization': `Bearer ${API_KEY}`,'Content-Type': 'application/json'}
});export default {async chatCompletion(prompt, model = 'deepseek-chat', maxTokens = 2048) {try {const response = await deepseekApi.post('/chat/completions', {model,messages: [{ role: 'user', content: prompt }],max_tokens: maxTokens});return response.data;} catch (error) {console.error('调用 DeepSeek API 时出错', error);throw error;}}};
4.第四步
编写前端,调用deepseek.js
<template><div class="deepseek-container"><!-- 回答展示区域 --><div class="response-area" v-if="response || isLoading"><div class="message-container"><!-- 头像区域 --><div class="message-avatar"><img src="@/assets/imgs/chat.png" alt="DeepSeek" class="avatar-img" /></div><!-- 消息内容区域 --><div class="message-content"><!-- 加载状态显示动画 --><div v-if="isLoading" class="loading-animation"><div class="loading-dot"></div><div class="loading-dot"></div><div class="loading-dot"></div><span class="loading-text">加载时间较长,请耐心等待</span></div><!-- 正常状态下显示格式化后的响应内容 --><div v-else class="markdown-content" v-html="formatResponse(response)"></div></div></div></div><!-- 输入区域 --><div class="input-container"><div class="input-box"><!-- 文本输入框 --><textareav-model="userInput"placeholder="有什么我可以帮助你的吗?"@keydown.enter.exact.prevent="askDeepSeek":disabled="isLoading"></textarea><!-- 发送按钮 --><button@click="askDeepSeek":disabled="isLoading || !userInput.trim()"class="send-button":class="{ 'button-loading': isLoading }"><span v-if="!isLoading"><!-- 发送图标 --><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg></span><!-- 加载时显示旋转动画 --><span v-else class="loading-spinner"></span></button></div><!-- 底部提示信息 --><div class="footer-note">AI小助手 可能会产生不准确的信息,请谨慎验证</div></div></div>
</template><script>
import { ref } from 'vue';
import deepseekService from '@/api/deepseek'; // 引入DeepSeek API服务
import { marked } from 'marked'; // Markdown解析库export default {setup() {// 定义响应式变量const userInput = ref(''); // 用户输入const response = ref(''); // API响应const isLoading = ref(false); // 加载状态/*** 格式化响应文本* @param {string} text - 原始文本* @returns {string} 格式化后的HTML*/const formatResponse = (text) => {if (!text) return '';// 1. 先处理Markdown的特殊结构(标题、列表等)// 2. 去除多余空行,但保留列表项和段落间的必要间距const cleanedText = text.replace(/^\s+|\s+$/g, '') // 去除首尾空白.replace(/\n{1,}/g, '\n') // 3个以上换行替换为2个.replace(/(\n\s*){2,}(\*|\-|\d+\.)/g, '\n$1') // 列表项前的多余空行.replace(/(#+)\s+\n/g, '$0 ') // 标题后的空行.replace(/\n{1,}/g, '\n'); // 最终确保最多连续2个换行return marked.parse(cleanedText);};/*** 向DeepSeek API提问*/const askDeepSeek = async () => {// 空输入或正在加载时不做处理if (!userInput.value.trim() || isLoading.value) return;isLoading.value = true; // 开始加载response.value = ''; // 清空之前的响应try {// 调用API获取响应const result = await deepseekService.chatCompletion(userInput.value);response.value = result.choices[0].message.content; // 获取响应内容} catch (error) {// 错误处理response.value = '**出错啦**\n\n抱歉,获取回答时出现问题,请稍后再试。';console.error('API调用错误:', error);} finally {isLoading.value = false; // 结束加载}};// 暴露给模板使用的变量和方法return {userInput,response,isLoading,askDeepSeek,formatResponse,};},
};
</script><style scoped>
/* 主容器样式 */
.deepseek-container {max-width: 800px;margin: 0 auto;padding: 20px;font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;color: #333;
}/* 响应区域样式 */
.response-area {margin-bottom: 20px;border-radius: 8px;background-color: #f9f9f9; /* 浅背景以提高可读性 */padding: 15px; /* 增加内边距 */box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 加深阴影 */
}/* 消息容器布局 */
.message-container {display: flex;padding: 0; /* 移除默认填充 */gap: 12px; /* 元素间距 */
}/* 头像样式 */
.message-avatar {flex-shrink: 0; /* 防止头像被压缩 */
}.avatar-img {width: 40px; /* 增大头像 */height: 40px;border-radius: 50%;object-fit: cover; /* 确保适配 */
}/* 消息内容样式 */
.message-content {flex-grow: 1; /* 占据剩余空间 */line-height: 1.5; /* 提高可读性 */text-align: left;
}/* Markdown内容样式 */
.markdown-content {white-space: normal; /* 不保留换行 */word-break: break-word; /* 长单词换行 */text-align: left;font-size: 14px; /* 调整字体大小 */
}/* 段落样式 */
.markdown-content :deep(p) {margin: 0.2em 0;line-height: 1.5;
}/* 列表样式 */
.markdown-content :deep(ul),
.markdown-content :deep(ol) {padding-left: 1.5em;margin: 0.5em 0;
}/* 行内代码样式 */
.markdown-content :deep(code) {background-color: #f0f0f0; /* 代码背景 */padding: 2px 4px;border-radius: 4px;font-family: monospace; /* 等宽字体 */
}/* 代码块样式 */
.markdown-content :deep(pre) {background-color: #f8f8f8; /* 代码块背景 */padding: 12px;border-radius: 6px;overflow-x: auto; /* 水平滚动 */margin: 0.75em 0; /* 代码块周围的边距 */
}/* 引用块样式 */
.markdown-content :deep(blockquote) {border-left: 3px solid #ddd; /* 引用块 */padding-left: 12px;margin: 0.75em 0;color: #666; /* 引用文本颜色 */
}/* 输入容器样式 */
.input-container {position: relative;
}/* 输入框样式 */
.input-box {position: relative;border: 1px solid #ddd;border-radius: 8px;padding: 8px;background-color: white;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}/* 文本输入框样式 */
textarea {width: 100%;min-height: 80px;border: none;resize: none; /* 禁止调整大小 */outline: none; /* 去除轮廓 */padding: 8px 40px 8px -40px; /* 右侧留出按钮空间 */font-size: 16px;line-height: 1.5;
}/* 发送按钮样式 */
.send-button {position: absolute;right: 12px;bottom: 12px;width: 36px;height: 36px;border-radius: 50%;background-color: #1a73e8;color: white;border: none;cursor: pointer;display: flex;align-items: center;justify-content: center;transition: all 0.2s; /* 过渡效果 */
}/* 禁用状态按钮样式 */
.send-button:disabled {background-color: #ccc;cursor: not-allowed;
}/* 悬停状态按钮样式 */
.send-button:not(:disabled):hover {background-color: #0d5bba;
}/* 加载状态按钮样式 */
.button-loading {background-color: #0d5bba;
}/* 加载旋转动画 */
.loading-spinner {width: 20px;height: 20px;border: 2px solid rgba(255, 255, 255, 0.3);border-radius: 50%;border-top-color: white;animation: spin 1s ease-in-out infinite; /* 旋转动画 */
}/* 加载点动画容器 */
.loading-animation {display: flex;gap: 6px; /* 加载点之间的间隔 */padding: 8px 0;
}/* 单个加载点样式 */
.loading-dot {width: 8px;height: 8px;background-color: #aaa;border-radius: 50%;animation: bounce 1.4s infinite ease-in-out; /* 弹跳动画 */
}/* 第二个加载点延迟 */
.loading-dot:nth-child(2) {animation-delay: 0.2s;
}
.loading-text {margin-left: 10px; /* 与加载动画保持间隔 */font-size: 14px; /* 文本大小 */color: #aaaaaa; /* 文本颜色,可根据需要调整 */align-self: center; /* 垂直居中 */
}
/* 第三个加载点延迟 */
.loading-dot:nth-child(3) {animation-delay: 0.4s;
}/* 底部提示样式 */
.footer-note {margin-top: 8px;font-size: 12px;color: #b4d5ff;text-align: center;
}
.footer-note:hover {color:#0742b1;
}
/* 旋转动画定义 */
@keyframes spin {to {transform: rotate(360deg);}
}/* 弹跳动画定义 */
@keyframes bounce {0%, 80%, 100% {transform: translateY(0);}40% {transform: translateY(-8px);}
}
</style>
效果展示