基于Vue.js的Baichuan-M2-32B医疗聊天前端开发指南

基于Vue.js的Baichuan-M2-32B医疗聊天前端开发指南 基于Vue.js的Baichuan-M2-32B医疗聊天前端开发指南1. 引言医疗咨询场景对前端界面有着特殊的要求既要保证专业严谨的医疗信息展示又要提供友好易用的交互体验。Baichuan-M2-32B作为专业的医疗增强推理模型能够提供高质量的医疗咨询回答而一个优秀的前端界面则是连接用户与AI医疗助手的关键桥梁。本文将带你从零开始开发一个基于Vue.js的医疗聊天前端应用实现与后端vLLM服务的WebSocket通信、消息历史管理和富文本渲染等功能。无论你是前端开发新手还是有一定经验的开发者都能通过本指南快速搭建一个专业的医疗咨询界面。2. 环境准备与项目搭建2.1 开发环境要求在开始之前确保你的开发环境满足以下要求Node.js 16.0 或更高版本npm 或 yarn 包管理器Vue.js 3.0一个可用的Baichuan-M2-32B后端服务基于vLLM部署2.2 创建Vue.js项目使用Vue CLI快速创建新项目npm create vuelatest medical-chat-frontend cd medical-chat-frontend npm install2.3 安装必要依赖安装项目所需的额外依赖包npm install axios socket.io-client marked dayjs npm install -D types/socket.io-client这些依赖包分别用于axios: HTTP请求处理socket.io-client: WebSocket通信marked: Markdown格式渲染dayjs: 时间日期处理3. 核心功能实现3.1 WebSocket通信模块创建src/utils/websocket.js文件实现与后端的实时通信import { io } from socket.io-client; class MedicalChatSocket { constructor() { this.socket null; this.isConnected false; this.messageCallbacks []; } connect(serverUrl) { this.socket io(serverUrl, { transports: [websocket], timeout: 10000 }); this.socket.on(connect, () { this.isConnected true; console.log(Connected to medical chat server); }); this.socket.on(disconnect, () { this.isConnected false; console.log(Disconnected from server); }); this.socket.on(medical_response, (data) { this.messageCallbacks.forEach(callback callback(data)); }); this.socket.on(error, (error) { console.error(WebSocket error:, error); }); } sendMessage(message) { if (this.isConnected this.socket) { this.socket.emit(medical_query, { message: message, timestamp: new Date().toISOString() }); return true; } return false; } onMessage(callback) { this.messageCallbacks.push(callback); } disconnect() { if (this.socket) { this.socket.disconnect(); this.isConnected false; } } } export default new MedicalChatSocket();3.2 消息数据管理创建src/composables/useChat.js组合式API来管理聊天状态import { ref, computed } from vue; import { saveChatHistory, loadChatHistory } from /utils/storage; export function useChat() { const messages ref([]); const currentMessage ref(); const isSending ref(false); // 加载历史消息 const loadHistory () { const history loadChatHistory(); if (history) { messages.value history; } }; // 添加新消息 const addMessage (message) { messages.value.push({ id: Date.now(), content: message.content, sender: message.sender, timestamp: new Date(), isThinking: message.isThinking || false }); // 保存到本地存储 saveChatHistory(messages.value); }; // 清空聊天记录 const clearChat () { messages.value []; localStorage.removeItem(medical_chat_history); }; // 计算未读消息数 const unreadCount computed(() { return messages.value.filter(msg msg.sender assistant !msg.read ).length; }); return { messages, currentMessage, isSending, loadHistory, addMessage, clearChat, unreadCount }; }3.3 富文本消息渲染组件创建src/components/ChatMessage.vue组件template div :class[message, message-${message.sender}] div classmessage-avatar img v-ifmessage.sender assistant src/assets/medical-bot.png altMedical Assistant img v-else src/assets/user-avatar.png altUser /div div classmessage-content div classmessage-header span classsender-name {{ message.sender assistant ? 医疗助手 : 我 }} /span span classmessage-time {{ formatTime(message.timestamp) }} /span /div div classmessage-body div v-ifmessage.isThinking classthinking-indicator div classthinking-dots span/span span/span span/span /div 医疗助手正在思考中... /div div v-else classmarkdown-content v-htmlrenderMarkdown(message.content) /div /div /div /div /template script setup import { marked } from marked; import { format } from date-fns; import { zhCN } from date-fns/locale; const props defineProps({ message: { type: Object, required: true } }); // 渲染Markdown内容 const renderMarkdown (content) { return marked.parse(content || ); }; // 格式化时间显示 const formatTime (timestamp) { return format(new Date(timestamp), HH:mm, { locale: zhCN }); }; /script style scoped .message { display: flex; margin-bottom: 1.5rem; padding: 0 1rem; } .message-assistant { flex-direction: row; } .message-user { flex-direction: row-reverse; } .message-avatar { width: 40px; height: 40px; border-radius: 50%; overflow: hidden; margin: 0 0.5rem; } .message-avatar img { width: 100%; height: 100%; object-fit: cover; } .message-content { max-width: 70%; background: var(--message-bg); border-radius: 12px; padding: 0.75rem; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .message-user .message-content { background: var(--primary-color); color: white; } .message-header { display: flex; justify-content: between; align-items: center; margin-bottom: 0.5rem; font-size: 0.8rem; opacity: 0.8; } .thinking-indicator { display: flex; align-items: center; color: #666; font-style: italic; } .thinking-dots { display: inline-flex; margin-right: 0.5rem; } .thinking-dots span { width: 4px; height: 4px; border-radius: 50%; background-color: #666; margin: 0 1px; animation: blink 1.4s infinite both; } .thinking-dots span:nth-child(2) { animation-delay: 0.2s; } .thinking-dots span:nth-child(3) { animation-delay: 0.4s; } keyframes blink { 0%, 80%, 100% { opacity: 0.2; } 40% { opacity: 1; } } /style4. 主聊天界面实现创建src/components/ChatInterface.vue主聊天组件template div classchat-container div classchat-header h2AI医疗咨询助手/h2 div classheader-actions button clickclearChat classbtn-clear title清空对话 ️ 清空 /button button clicktoggleConnection :class[btn-connection, isConnected ? connected : disconnected] {{ isConnected ? 已连接 : 未连接 }} /button /div /div div classchat-messages refmessagesContainer div v-ifmessages.length 0 classempty-state img src/assets/medical-welcome.png altWelcome h3欢迎使用AI医疗咨询助手/h3 p我可以帮助您解答医疗健康相关问题请随时提问/p div classexample-questions button v-for(question, index) in exampleQuestions :keyindex clicksendExampleQuestion(question) classexample-btn {{ question }} /button /div /div ChatMessage v-formessage in messages :keymessage.id :messagemessage / /div div classchat-input-container div classinput-wrapper textarea v-modelcurrentMessage keydown.entersendMessage placeholder请输入您的医疗问题... rows1 classmessage-input :disabled!isConnected || isSending /textarea button clicksendMessage :disabled!currentMessage.trim() || !isConnected || isSending classsend-button span v-ifisSending发送中.../span span v-else发送/span /button /div div classinput-footer span classconnection-status {{ connectionStatus }} /span span classmedical-disclaimer 温馨提示AI建议仅供参考如有紧急情况请及时就医 /span /div /div /div /template script setup import { ref, onMounted, onUnmounted, nextTick, computed } from vue; import ChatMessage from ./ChatMessage.vue; import chatSocket from /utils/websocket; import { useChat } from /composables/useChat; const { messages, currentMessage, isSending, addMessage, clearChat, loadHistory } useChat(); const messagesContainer ref(null); const isConnected ref(false); const exampleQuestions [ 感冒了应该怎么办, 如何预防高血压, 头痛可能是什么原因引起的, 健康饮食有哪些建议 ]; const connectionStatus computed(() { if (!isConnected.value) return 连接已断开正在尝试重连...; if (isSending.value) return AI正在思考中...; return 连接正常可以提问; }); // 发送消息 const sendMessage async () { if (!currentMessage.value.trim() || !isConnected.value || isSending.value) { return; } const userMessage currentMessage.value.trim(); currentMessage.value ; isSending.value true; // 添加用户消息 addMessage({ content: userMessage, sender: user, timestamp: new Date() }); // 添加思考中的助手消息 const thinkingMessage { content: , sender: assistant, timestamp: new Date(), isThinking: true }; addMessage(thinkingMessage); scrollToBottom(); // 通过WebSocket发送消息 const success chatSocket.sendMessage(userMessage); if (!success) { isSending.value false; messages.value messages.value.filter(msg !msg.isThinking); addMessage({ content: 发送失败请检查网络连接, sender: system, timestamp: new Date() }); } }; // 发送示例问题 const sendExampleQuestion (question) { currentMessage.value question; sendMessage(); }; // 清空聊天 const clearChatHistory () { if (confirm(确定要清空所有聊天记录吗)) { clearChat(); } }; // 滚动到底部 const scrollToBottom () { nextTick(() { if (messagesContainer.value) { messagesContainer.value.scrollTop messagesContainer.value.scrollHeight; } }); }; // 连接状态管理 const toggleConnection () { if (isConnected.value) { chatSocket.disconnect(); } else { chatSocket.connect(ws://your-backend-url); } }; // 初始化 onMounted(() { loadHistory(); // 监听WebSocket消息 chatSocket.onMessage((data) { isSending.value false; // 移除思考中的消息 messages.value messages.value.filter(msg !msg.isThinking); // 添加助手回复 addMessage({ content: data.response, sender: assistant, timestamp: new Date() }); scrollToBottom(); }); // 连接WebSocket chatSocket.connect(ws://your-backend-url); }); onUnmounted(() { chatSocket.disconnect(); }); /script style scoped .chat-container { display: flex; flex-direction: column; height: 100vh; max-width: 1200px; margin: 0 auto; background: white; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); } .chat-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .header-actions { display: flex; gap: 0.5rem; } .btn-clear, .btn-connection { padding: 0.5rem 1rem; border: none; border-radius: 6px; cursor: pointer; font-size: 0.9rem; } .btn-clear { background: rgba(255, 255, 255, 0.2); color: white; } .btn-connection.connected { background: #4caf50; color: white; } .btn-connection.disconnected { background: #f44336; color: white; } .chat-messages { flex: 1; overflow-y: auto; padding: 1rem; background: #f8f9fa; } .empty-state { text-align: center; padding: 3rem 1rem; color: #666; } .empty-state img { width: 120px; margin-bottom: 1rem; } .example-questions { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin-top: 2rem; } .example-btn { padding: 0.5rem 1rem; border: 1px solid #ddd; border-radius: 20px; background: white; cursor: pointer; font-size: 0.9rem; transition: all 0.2s; } .example-btn:hover { background: #667eea; color: white; border-color: #667eea; } .chat-input-container { padding: 1rem; background: white; border-top: 1px solid #e9ecef; } .input-wrapper { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; } .message-input { flex: 1; padding: 0.75rem; border: 1px solid #ddd; border-radius: 8px; resize: none; font-family: inherit; font-size: 1rem; line-height: 1.5; } .message-input:focus { outline: none; border-color: #667eea; } .send-button { padding: 0.75rem 1.5rem; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; } .send-button:disabled { background: #ccc; cursor: not-allowed; } .input-footer { display: flex; justify-content: space-between; font-size: 0.8rem; color: #666; } .medical-disclaimer { color: #f44336; font-weight: 500; } /style5. 医疗特色功能增强5.1 症状描述表单组件创建src/components/SymptomForm.vue组件帮助用户更准确地描述症状template div classsymptom-form h4症状描述助手/h4 form submit.preventgenerateSymptomDescription div classform-group label主要症状/label input v-modelsymptom.mainSymptom placeholder如头痛、咳嗽、发热等 /div div classform-group label开始时间/label input typedate v-modelsymptom.startTime /div div classform-group label严重程度/label select v-modelsymptom.severity option valuemild轻微/option option valuemoderate中度/option option valuesevere严重/option /select /div div classform-group label伴随症状/label div classcheckbox-group label v-forsymptom in accompanyingSymptoms :keysymptom input typecheckbox :valuesymptom v-modelsymptom.accompanying {{ symptom }} /label /div /div button typesubmit生成症状描述/button /form /div /template script setup import { ref } from vue; const symptom ref({ mainSymptom: , startTime: , severity: mild, accompanying: [] }); const accompanyingSymptoms [ 发热, 乏力, 食欲不振, 恶心, 呕吐, 腹泻, 头晕, 心慌, 呼吸困难, 胸痛, 关节痛 ]; const generateSymptomDescription () { const description 我${symptom.value.mainSymptom}已经${ symptom.value.startTime ? 从${symptom.value.startTime}开始 : 最近 }严重程度${{ mild: 轻微, moderate: 中度, severe: 严重 }[symptom.value.severity]}${symptom.value.accompanying.length 0 ? 伴有${symptom.value.accompanying.join(、)} : }。请问可能是什么原因需要注意什么; emit(symptom-described, description); }; const emit defineEmits([symptom-described]); /script style scoped .symptom-form { padding: 1rem; background: #f8f9fa; border-radius: 8px; margin-bottom: 1rem; } .form-group { margin-bottom: 1rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 500; } .checkbox-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 0.5rem; } .checkbox-group label { display: flex; align-items: center; font-weight: normal; } input[typecheckbox] { margin-right: 0.5rem; } /style6. 部署与优化建议6.1 生产环境部署创建vue.config.js进行生产环境配置const { defineConfig } require(vue/cli-service); module.exports defineConfig({ transpileDependencies: true, productionSourceMap: false, configureWebpack: { optimization: { splitChunks: { chunks: all, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: vendors, chunks: all } } } } }, devServer: { proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } });6.2 性能优化建议代码分割使用Vue的异步组件实现路由懒加载图片优化使用WebP格式图片实现响应式图片加载缓存策略合理配置HTTP缓存头使用Service Worker进行资源缓存监控告警集成前端监控工具实时监控应用性能7. 总结通过本指南我们完成了一个基于Vue.js的医疗聊天前端应用的开发。这个应用不仅实现了基本的聊天功能还针对医疗场景进行了特殊优化包括症状描述辅助、富文本医疗信息展示、连接状态管理等。实际开发中你可能还需要根据具体需求添加更多功能比如文件上传用于上传医疗影像图片、语音输入输出、多语言支持等。最重要的是要始终牢记医疗应用的特殊性确保信息的准确性和界面的友好性。这个前端应用与Baichuan-M2-32B后端结合能够为用户提供专业、便捷的医疗咨询服务。记得在实际部署时要配置正确的后端服务地址并确保WebSocket连接的安全性。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。