如何在毕业设计系统里增加AI模块

张开发
2026/4/21 12:20:22 15 分钟阅读

分享文章

如何在毕业设计系统里增加AI模块
# VUE 项目集成 AI 对话功能指南本文档介绍如何将 Vue 项目中的 AI 对话功能集成到 VUE 项目中。## 一、效果预览集成完成后你的应用将具备以下功能- 悬浮按钮点击打开 AI 对话窗口- 支持发送消息与 AI 交互- 流式响应展示打字机效果- 支持清空对话历史- 消息格式化显示支持 Markdown### 步骤 1创建 AI 对话组件在 vue 项目的 src/component 目录下创建 ai-chat.vuetemplate div classai-chat-container div classchat-content refchatContent div v-ifmessages.length 0 classwelcome-message div classwelcome-icon i classel-icon-chat-dot-round/i /div p classwelcome-text你好我是AI助手有什么问题可以问我/p p classwelcome-subtext我可以回答问题、提供建议、协助学习和解决各种挑战/p /div div v-for(message, index) in messages :keyindex classmessage-wrapper div v-ifmessage.role user classuser-message div classmessage-bubble user-bubble div classmessage-text{{ message.content }}/div /div div classmessage-avatar user-avatar i classel-icon-user-solid/i /div /div div v-else classai-message div classmessage-avatar ai-avatar i classel-icon-monitor/i /div div classmessage-bubble ai-bubble div classmessage-text v-htmlformatMessage(message.content)/div div v-ifmessage.streaming classstreaming-indicator span classdot/span span classdot/span span classdot/span /div div v-ifmessage.isRetryable !message.streaming classretry-section el-button typetext clickretryMessage(index) classretry-button i classel-icon-refresh/i 重新发起对话 /el-button /div /div /div /div div v-ifloading classloading-message i classel-icon-loading/i spanAI 正在思考.../span /div /div div classchat-input-area div classinput-top !-- el-select v-modelcurrentModel placeholder选择模型 sizesmall classmodel-select el-option v-formodel in models :keymodel.value :labelmodel.label :valuemodel.value /el-option /el-select -- el-button typetext sizesmall clickclearChatHistory iconel-icon-delete classclear-btn 清空对话 /el-button /div div classinput-row el-input v-modelinputMessage typetextarea :rows2 placeholder请输入您的问题...Enter 发送CtrlEnter 换行 resizenone keydown.enter.nativehandleEnterKey :disabledloading classchat-textarea /el-input div classsend-btns el-button typeprimary sizesmall clicksendMessage :loadingloading :disabled!inputMessage.trim() || loading 发送/el-button el-button typedanger sizesmall plain clickstopMessage :disabled!loading 停止/el-button /div /div /div /div /template script export default { name: AIChat, data() { return { messages: [], inputMessage: , loading: false, currentModel: zhipu/glm-4.1v-thinking-flash, currentXhr: null, models: [ { value: moonshotai/kimi-k2, label: Moonshot Kimi K2 }, { value: deepseek/deepseek-r1-0528, label: DeepSeek R1-0528 }, { value: deepseek/deepseek-v3-0324, label: DeepSeek V3-0324 }, { value: deepseek/deepseek-r1-32b, label: DeepSeek R1-32B }, { value: deepseek/deepseek-r1-70b, label: DeepSeek R1-70B }, { value: google/gemini-2.0-flash-exp, label: Google Gemini 2.0 Flash }, { value: google/gemma-3-27b, label: Google Gemma 3-27B }, { value: qwen/qwq-32b, label: Qwen QWQ-32B }, { value: qwen/qwen2.5-7b, label: Qwen 2.5-7B }, { value: qwen/qwen2.5-72b, label: Qwen 2.5-72B }, { value: qwen/qwen2.5-vl-32b, label: Qwen 2.5-VL-32B }, { value: qwen/qwen2.5-vl-72b, label: Qwen 2.5-VL-72B }, { value: zhipu/glm-4-9b, label: Zhipu GLM-4-9B }, { value: zhipu/glm-4-flash, label: Zhipu GLM-4 Flash }, { value: zhipu/glm-4v-flash, label: Zhipu GLM-4V Flash }, { value: zhipu/glm-4.1v-thinking-flash, label: Zhipu GLM-4.1V Thinking Flash }, { value: qwen/qwen3-30b-a3b, label: Qwen 3-30B-A3B }, { value: qwen/qwen3-14b, label: Qwen 3-14B }, { value: qwen/qwen3-coder, label: Qwen 3 Coder }, { value: zhipu/glm-4.5-flash, label: Zhipu GLM-4.5 Flash }, { value: openai/gpt-oss-20b, label: OpenAI GPT-OSS-20B }, { value: tencent/hunyuan-a13b, label: Tencent Hunyuan-A13B } ] }; }, methods: { async sendMessage() { if (!this.inputMessage.trim() || this.loading) return; const userMessage this.inputMessage.trim(); this.messages.push({ role: user, content: userMessage }); this.inputMessage ; this.loading true; const aiMessageIndex this.messages.push({ role: ai, content: , streaming: true }) - 1; try { try { const fullResponse await this.callAIAPI(userMessage); const typewriterEffect async () { let currentText ; const speed 30; for (let i 0; i fullResponse.length; i) { if (this.currentXhr null) break; currentText fullResponse[i]; this.messages[aiMessageIndex].content currentText; await new Promise(resolve setTimeout(resolve, speed)); } this.messages[aiMessageIndex].streaming false; }; await typewriterEffect(); } catch (error) { if (!error.message.includes(请求已取消)) throw error; } } catch (error) { this.messages[aiMessageIndex].content 获取AI回答失败: ${error.message}; this.messages[aiMessageIndex].streaming false; this.messages[aiMessageIndex].isRetryable error.message.includes(504) || error.message.includes(请求超时); this.$message.error(获取AI回答失败: ${error.message}); console.error(AI API调用失败:, error); } finally { this.loading false; this.scrollToBottom(); } }, async callAIAPI(prompt) { const apiKey Bearer sk-bbaa7b7775a64deb854b3f1755c0591f; const url https://platform.aitools.cfd/api/v1/chat/completions; const data { model: this.currentModel, messages: [{ role: user, content: prompt }], stream: false }; return new Promise((resolve, reject) { const xhr new XMLHttpRequest(); this.currentXhr xhr; xhr.open(POST, url); xhr.setRequestHeader(Authorization, apiKey); xhr.setRequestHeader(Content-Type, application/json); xhr.timeout 100000; xhr.responseType json; xhr.onreadystatechange () { if (xhr.readyState XMLHttpRequest.DONE) { if (xhr.status 504) { reject(new Error(请求超时(504)服务器暂时无法响应请重试)); return; } if (xhr.status 200) { try { const response xhr.response; if (response response.choices response.choices.length 0) { resolve(response.choices[0].message?.content || ); } else { reject(new Error(AI返回了空响应)); } } catch (e) { reject(new Error(解析AI响应失败)); } } else { reject(new Error(API请求失败状态码: ${xhr.status})); } } }; xhr.onerror () reject(new Error(网络错误请检查您的网络连接)); xhr.onabort () { this.currentXhr null; reject(new Error(请求已取消)); }; xhr.ontimeout () { this.currentXhr null; reject(new Error(请求超时请稍后再试)); }; xhr.send(JSON.stringify(data)); }); }, stopMessage() { if (this.currentXhr) { this.currentXhr.abort(); this.loading false; this.currentXhr null; if (this.messages.length 0) { const lastMessage this.messages[this.messages.length - 1]; if (lastMessage.role ai lastMessage.streaming) { lastMessage.streaming false; lastMessage.isUserCancelled true; if (!lastMessage.content.trim()) lastMessage.content 对话已取消; } } this.$message.info(对话已取消); } }, retryMessage(index) { if (index 0 this.messages[index - 1].role user) { const userMessage this.messages[index - 1].content; this.messages.splice(index, 1); this.inputMessage userMessage; this.sendMessage(); } else { this.$message.warning(无法找到对应的问题进行重试); } }, clearChatHistory() { this.$confirm(确定要清空所有对话历史吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(() { this.messages []; this.$message.success(对话历史已清空); }).catch(() {}); }, handleEnterKey(event) { if (event.ctrlKey || event.metaKey) return; event.preventDefault(); this.sendMessage(); }, scrollToBottom() { this.$nextTick(() { const chatContent this.$refs.chatContent; if (chatContent) chatContent.scrollTop chatContent.scrollHeight; }); }, formatMessage(content) { return content .replace(/^### (.*$)/gm, h3$1/h3) .replace(/^## (.*$)/gm, h2$1/h2) .replace(/^# (.*$)/gm, h1$1/h1) .replace(/\*\*(.*?)\*\*/g, strong$1/strong) .replace(/\*(.*?)\*/g, em$1/em) .replace(/([\s\S]*?)/g, precode$1/code/pre) .replace(/(.*?)/g, code$1/code) .replace(/\n/g, br); } }, watch: { messages: { handler() { this.scrollToBottom(); }, deep: true } }, mounted() { this.scrollToBottom(); } }; /script style langless scoped .ai-chat-container { display: flex; flex-direction: column; height: 100%; background: #fafafa; } .chat-content { flex: 1; overflow-y: auto; padding: 20px; ::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #ddd; border-radius: 3px; :hover { background: #bbb; } } } .welcome-message { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 260px; text-align: center; .welcome-icon { width: 64px; height: 64px; border-radius: 50%; background: #ecf5ff; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; i { font-size: 30px; color: #409eff; } } .welcome-text { font-size: 16px; color: #303133; font-weight: 500; margin: 0 0 6px 0; } .welcome-subtext { font-size: 13px; color: #999; margin: 0; } } .message-wrapper { margin-bottom: 18px; } .user-message, .ai-message { display: flex; align-items: flex-start; gap: 10px; } .user-message { justify-content: flex-end; .message-bubble { order: 1; } .message-avatar { order: 2; } } .message-avatar { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 16px; .user-avatar { background: #409eff; color: #fff; } .ai-avatar { background: #f0f2f5; color: #606266; border: 1px solid #e4e7ed; } } .message-bubble { max-width: 70%; padding: 12px 16px; border-radius: 12px; word-wrap: break-word; .message-text { font-size: 14px; line-height: 1.7; white-space: pre-wrap; } h1, h2, h3 { margin: 10px 0 6px 0; font-weight: 600; } h1 { font-size: 18px; } h2 { font-size: 16px; } h3 { font-size: 15px; } strong { font-weight: 600; } code { background: rgba(0, 0, 0, 0.06); padding: 2px 5px; border-radius: 3px; font-size: 13px; font-family: Consolas, Monaco, monospace; } pre { background: #f5f7fa; padding: 12px; border-radius: 6px; overflow-x: auto; margin: 10px 0; code { background: transparent; padding: 0; } } } .user-bubble { background: #409eff; color: #fff; border-bottom-right-radius: 4px; } .ai-bubble { background: #fff; color: #303133; border: 1px solid #ebeef5; border-bottom-left-radius: 4px; } .streaming-indicator { display: inline-flex; align-items: center; margin-top: 8px; .dot { width: 5px; height: 5px; border-radius: 50%; background: #409eff; margin: 0 3px; animation: bounce 1.4s infinite ease-in-out both; :nth-child(1) { animation-delay: -0.32s; } :nth-child(2) { animation-delay: -0.16s; } } } .retry-section { margin-top: 10px; .retry-button { color: #409eff; font-size: 12px; padding: 4px 10px; :hover { background: #ecf5ff; } } } .loading-message { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 16px; color: #999; font-size: 13px; background: #fff; border: 1px solid #ebeef5; border-radius: 20px; max-width: 160px; margin: 0 auto; i { font-size: 16px; color: #409eff; } } .chat-input-area { padding: 12px 16px; background: #fff; border-top: 1px solid #ebeef5; .input-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; .model-select { width: 220px; } .clear-btn { color: #999; :hover { color: #f56c6c; } } } .input-row { display: flex; gap: 10px; align-items: flex-end; .chat-textarea { flex: 1; /deep/ .el-textarea__inner { border-radius: 8px; border-color: #dcdfe6; font-size: 14px; line-height: 1.5; padding: 10px 12px; :focus { border-color: #409eff; } } } .send-btns { display: flex; flex-direction: column; gap: 6px; .el-button { border-radius: 6px; min-width: 64px; } } } } keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1.2); } } media screen and (max-width: 768px) { .message-bubble { max-width: 85%; } .chat-input-area { .input-top { flex-wrap: wrap; gap: 8px; } .model-select { width: 100%; } .input-row { flex-direction: column; .send-btns { flex-direction: row; align-self: flex-end; } } } } /style### 步骤 2在app.vue增加悬浮入口template div classapp-container router-view/router-view div classai-fab clickopenAIChatDialog titleAI助手 i classel-icon-chat-dot-round/i span classai-fab-label智能助手/span /div el-dialog title智能助手 :visible.syncaiChatDialogVisible width720px top6vh :close-on-click-modalfalse :close-on-press-escapetrue custom-classai-chat-dialog :append-to-bodytrue div classai-chat-body ai-chat v-ifaiChatDialogVisible / /div /el-dialog /div /template script import aiChat from /component/aiChat.vue; export default { name: VueTemplateApp, // 组件名称为 VueTemplateApp components: { aiChat }, data() { return { sysName: oh!, aiChatDialogVisible: false }; }, async mounted() { }, methods: { // 打开AI问答对话框 openAIChatDialog() { this.aiChatDialogVisible true; } }, }; /script style langless scoped .app-container { width: 100%; height: 100%; background-size: cover; background-position: center; position: relative; } .ai-fab { position: fixed; bottom: 32px; right: 32px; z-index: 9999; width: 52px; height: 52px; border-radius: 16px; background: #409eff; color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; cursor: pointer; box-shadow: 0 4px 14px rgba(64, 158, 255, 0.35); transition: all 0.25s; i { font-size: 22px; line-height: 1; } .ai-fab-label { font-size: 10px; font-weight: 600; letter-spacing: 1px; line-height: 1; } :hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(64, 158, 255, 0.45); } :active { transform: scale(0.93); } } .ai-chat-body { height: calc(80vh - 120px); overflow: hidden; } media screen and (max-width: 768px) { .ai-fab { bottom: 20px; right: 20px; width: 46px; height: 46px; border-radius: 14px; i { font-size: 20px; } } } /style style .ai-chat-dialog { border-radius: 12px; overflow: hidden; } .ai-chat-dialog .el-dialog__header { background: #f8fafc; border-bottom: 1px solid #ebeef5; padding: 14px 20px; } .ai-chat-dialog .el-dialog__title { font-size: 15px; font-weight: 600; color: #303133; } .ai-chat-dialog .el-dialog__body { padding: 0; } media screen and (max-width: 768px) { .ai-chat-dialog { width: 95% !important; margin-top: 5vh !important; } } /style

更多文章