From 55ad1b125f8c9bff5d413eb2a696f811349f9d92 Mon Sep 17 00:00:00 2001 From: liuzhiyuan <> Date: Sat, 30 Aug 2025 13:10:25 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=A4=A7=E5=B1=8F?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=AD=A6=E6=9C=AF=E4=BA=A7=E5=87=BA=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=EF=BC=81=E8=B0=83=E6=95=B4=E6=99=BA=E8=83=BD=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Dashboard.vue | 209 ++++++++++++++++++++++++++--------- src/config.js | 9 +- 2 files changed, 166 insertions(+), 52 deletions(-) diff --git a/src/components/Dashboard.vue b/src/components/Dashboard.vue index 2038ab6..72cb33d 100644 --- a/src/components/Dashboard.vue +++ b/src/components/Dashboard.vue @@ -46,8 +46,8 @@ -
-

学术产出

+
+

学术产出

+
+ + + +
+
+
-
+
@@ -367,6 +375,8 @@ const chatMessagesRef = ref(null) const userInput = ref('') const isLoading = ref(false) const chatMessages = ref([]) +let typingInterval = null +let thinkingInterval = null // DeepSeek API 调用函数 const sendMessage = async () => { @@ -374,35 +384,41 @@ const sendMessage = async () => { // 添加用户消息到对话 const userMessage = userInput.value.trim(); - chatMessages.value.push({ role: 'user', content: userMessage }); + chatMessages.value.push({ + role: 'user', + content: userMessage, + isTyping: false, + isThinking: false + }); userInput.value = ''; - // 立即滚动到底部(用户消息) - await nextTick(); - scrollToBottom(); + // 立即滚动到底部 + // await nextTick(); + // scrollToBottom(); // 设置加载状态并添加占位消息 isLoading.value = true; - chatMessages.value.push({ role: 'assistant', content: '思考中...' }); + chatMessages.value.push({ + role: 'assistant', + content: '', + isTyping: false, + isThinking: true, + isHidden: true // 添加一个标志表示消息暂时隐藏 + }); const aiMessageIndex = chatMessages.value.length - 1; try { - // 只保留最近5条消息以优化性能 - const apiMessages = chatMessages.value - .slice(-5) - .map(msg => ({ role: msg.role, content: msg.content })); - // 调用API(启用流式传输) - const response = await fetch('https://api.deepseek.com/chat/completions', { + const response = await fetch(`${getChatApiUrl()}/chat/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer sk-06801499dd52426fa7cf3b0670931e3a' }, body: JSON.stringify({ - model: 'deepseek-chat', - messages: apiMessages, - stream: true // 关键优化:启用流式响应 + model: "deepseek-r1:7b", + messages: [{ role: "user", content: userMessage }], + options: { temperature: 0.6 }, + keep_thinking: false }) }); @@ -412,37 +428,75 @@ const sendMessage = async () => { // 流式处理数据 const reader = response.body.getReader(); - let fullResponse = ''; const decoder = new TextDecoder(); - + let buffer = ""; + let fullResponse = ""; + + // 替换思考中消息为实际响应消息 + chatMessages.value[aiMessageIndex] = { + role: 'assistant', + content: '', + isTyping: false, + isThinking: false, + isHidden: true // 添加一个标志表示消息暂时隐藏 + }; + // 立即滚动到底部 + await nextTick(); + scrollToBottom(); + // 等待API响应完成 while (true) { const { done, value } = await reader.read(); if (done) break; - - // 解析流式数据(假设API返回ndjson格式) - const chunk = decoder.decode(value); - const lines = chunk.split('\n').filter(line => line.trim()); - + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; + for (const line of lines) { + if (!line.trim()) continue; + try { - const data = JSON.parse(line.replace('data: ', '')); - if (data.choices?.[0]?.delta?.content) { - fullResponse += data.choices[0].delta.content; - // 实时更新UI(逐字显示) - chatMessages.value[aiMessageIndex].content = md.render(fullResponse); - scrollToBottom(); + const obj = JSON.parse(line); + if (obj.message?.content) { + fullResponse += obj.message.content; } } catch (e) { - console.warn('解析流数据失败:', e); + console.warn('解析JSON失败:', e); } } } + // API响应完成后,设置isLoading为false + setTimeout(() => { + isLoading.value = false; + // 移除隐藏标志,开始打字机效果 + chatMessages.value[aiMessageIndex].isHidden = false; + + // 然后开始打字机效果 + let typingIndex = 0; + const typingInterval = setInterval(() => { + if (typingIndex < fullResponse.length) { + chatMessages.value[aiMessageIndex].content = + fullResponse.substring(0, typingIndex + 1); + typingIndex++; + scrollToBottom(); + } else { + clearInterval(typingInterval); + } + }, 20); + }, 100) + + } catch (error) { console.error('请求失败:', error); - chatMessages.value[aiMessageIndex].content = '抱歉,回答时遇到问题,请重试。'; - } finally { isLoading.value = false; + chatMessages.value[aiMessageIndex] = { + role: 'assistant', + content: '抱歉,回答时遇到问题,请重试。', + isTyping: false, + isThinking: false, + isHidden: false // 确保错误消息显示 + }; scrollToBottom(); } }; @@ -451,7 +505,7 @@ const sendMessage = async () => { const scrollToBottom = () => { nextTick().then(() => { if (chatMessagesRef.value) { - chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight; + chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight; } }); }; @@ -504,7 +558,7 @@ const dashboardData2 = ref(null); const loading = ref(true); // 引入API配置 -import { getApiBaseUrl } from '../config'; +import { getApiBaseUrl, getChatApiUrl } from '../config'; // 从API获取仪表盘数据 const fetchDashboardData = async () => { @@ -1087,14 +1141,27 @@ const initCharts = () => { '博士': outputLegendStatus.value[2], '硕士': outputLegendStatus.value[1], '学士': outputLegendStatus.value[0], - } + }, + top: 10 // 调整图例位置 + }, + grid: { + left: '3%', + right: '12%', + bottom: '3%', + top: '20%', // 增加顶部间距给图例 + containLabel: true }, - grid: { left: '3%', right: '12%', bottom: '3%', containLabel: true }, yAxis: { type: 'category', data: outputData.value.months, axisLine: { lineStyle: { color: '#fff' } }, - axisLabel: { color: '#fff' } + axisLabel: { + color: '#fff', + margin: 15 // 增加y轴标签边距 + }, + axisTick: { + alignWithLabel: true + } }, xAxis: { type: 'value', @@ -1106,7 +1173,8 @@ const initCharts = () => { type: 'dashed', color: 'rgba(255, 255, 255, 0.2)' } - } + }, + interval: 1 }, series: [ { @@ -1114,13 +1182,16 @@ const initCharts = () => { type: 'bar', data: outputData.value.doctor, itemStyle: { color: '#4080ff' }, - barWidth: '20%', - barGap: '20%', + barWidth: '25%', // 减小柱宽度 + barGap: '20%', // 增加柱子间距 label: { show: true, position: 'right', color: '#fff', - formatter: '{c}' + formatter: function(params) { + // 只显示非零值,避免0重叠 + return params.value > 0 ? params.value : ''; + } } }, { @@ -1128,13 +1199,16 @@ const initCharts = () => { type: 'bar', data: outputData.value.master, itemStyle: { color: 'rgb(107,187,196)' }, - barWidth: '20%', + barWidth: '25%', barGap: '20%', label: { show: true, position: 'right', color: '#fff', - formatter: '{c}' + formatter: function(params) { + // 只显示非零值,避免0重叠 + return params.value > 0 ? params.value : ''; + } } }, { @@ -1142,13 +1216,16 @@ const initCharts = () => { type: 'bar', data: outputData.value.bachelor, itemStyle: { color: 'rgb(107,187,19)' }, - barWidth: '20%', + barWidth: '25%', barGap: '20%', label: { show: true, position: 'right', color: '#fff', - formatter: '{c}' + formatter: function(params) { + // 只显示非零值,避免0重叠 + return params.value > 0 ? params.value : ''; + } } } ] @@ -1313,6 +1390,12 @@ const newsData = ref([]); // 组件卸载时清理定时器 onUnmounted(() => { + if (typingInterval) { + clearInterval(typingInterval); + } + if (thinkingInterval) { + clearInterval(thinkingInterval); + } window.removeEventListener('resize', () => {}); }) @@ -1766,7 +1849,31 @@ onUnmounted(() => { .assistant-panel .panel-header { display: flex; align-items: center; - cursor: move; /* 整个头部可拖动 */ - user-select: none; /* 防止拖动时选中文本 */ + cursor: move; + user-select: none; +} + +/* 思考消息样式 */ +.thinking-message { + display: flex; + align-items: center; +} + +.thinking-dots::after { + content: '...'; + display: inline-block; + width: 20px; + animation: blinkDots 1.5s infinite steps(4, end); + +} + +@keyframes blinkDots { + 0%, 100% { opacity: 0; } + 25% { opacity: 0.5; } + 50% { opacity: 1; } +} + +.message { + transition: all 0.3s ease; } \ No newline at end of file diff --git a/src/config.js b/src/config.js index 5a0c80c..2d70fb4 100644 --- a/src/config.js +++ b/src/config.js @@ -3,16 +3,23 @@ const env = import.meta.env.MODE || 'development'; const config = { development: { - apiBaseUrl: 'http://192.168.5.49:48080', + // apiBaseUrl: 'http://192.168.5.49:48080', + apiBaseUrl: 'http://36.103.199.218:48089', + chatApiUrl: 'http://36.103.199.218:8093' }, production: { apiBaseUrl: 'http://221.238.217.216:4160', + chatApiUrl: 'http://221.238.217.216:8093', // apiBaseUrl: 'http://36.103.199.218:48089', + // chatApiUrl: 'http://36.103.199.218:8093', } }; export const getApiBaseUrl = () => { return config[env].apiBaseUrl; }; +export const getChatApiUrl = () => { + return config[env].chatApiUrl; +} export default config; \ No newline at end of file