From 1268c2fd9c7d7d21d1c514e43112a378c47c2bcf Mon Sep 17 00:00:00 2001
From: ningbo <3301955438@qq.com>
Date: Thu, 14 Aug 2025 10:13:53 +0800
Subject: [PATCH] =?UTF-8?q?feat(aitutor):=20=E6=96=B0=E5=A2=9E=E8=81=8A?=
=?UTF-8?q?=E5=A4=A9=E5=8E=86=E5=8F=B2=E5=88=86=E9=A1=B5=E5=8A=A0=E8=BD=BD?=
=?UTF-8?q?=E5=92=8CMarkdown=E6=B8=B2=E6=9F=93=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
为聊天历史页面添加分页加载功能,支持加载更多历史消息
使用marked库实现AI回答的Markdown格式渲染
优化聊天历史对话框的样式和交互体验
---
package.json | 1 +
src/views/aitutor/chathistory/index.vue | 300 +++++++++++++++++++++++-
2 files changed, 297 insertions(+), 4 deletions(-)
diff --git a/package.json b/package.json
index 38422c2..b95bf71 100644
--- a/package.json
+++ b/package.json
@@ -63,6 +63,7 @@
"jspdf": "^2.5.2",
"lodash": "^4.17.21",
"mapv-three": "^1.0.18",
+ "marked": "^4.3.0",
"nprogress": "0.2.0",
"print-js": "^1.6.0",
"quill": "1.3.7",
diff --git a/src/views/aitutor/chathistory/index.vue b/src/views/aitutor/chathistory/index.vue
index 19455ec..42c9e8c 100644
--- a/src/views/aitutor/chathistory/index.vue
+++ b/src/views/aitutor/chathistory/index.vue
@@ -135,10 +135,30 @@
{{ message.answer }}
+ }" v-html="renderMarkdown(message.answer)">
+
+
+
+
+ 加载更多历史消息
+
+
+
+
+ 已经到底了
+
+
+
@@ -149,6 +169,7 @@ import { listStudent, getClassName } from "@/api/stuCQS/basedata/student";
import { getMessagesToAdmin } from "@/api/aitutor/chat";
import { listGrade } from "@/api/stuCQS/basedata/grade";
import { getDeptName } from "@/api/system/dept";
+import { marked } from 'marked';
export default {
name: "ChatHistory",
@@ -186,7 +207,11 @@ export default {
dialogVisible: false,
chatMessages: [],
chatLoading: false,
- currentStudent: null
+ currentStudent: null,
+ // 分页加载相关
+ loadMoreLoading: false,
+ hasMoreMessages: true,
+ isLoadingMore: false
};
},
created() {
@@ -264,13 +289,24 @@ export default {
this.chatLoading = true;
this.chatMessages = [];
+ // 重置分页状态
+ this.hasMoreMessages = true;
+ this.isLoadingMore = false;
+ this.loadMoreLoading = false;
+
getMessagesToAdmin({
- user: row.stuNo
+ user: row.stuNo,
+ limit: 20
}).then(response => {
console.log('对话记录API响应:', response);
if (response.code === 200 && response.data && response.data.data) {
- this.chatMessages = response.data.data;
+ // 按照created_at时间戳进行降序排序,最新的消息在最上面
+ this.chatMessages = response.data.data.sort((a, b) => b.created_at - a.created_at);
+ // 如果返回的消息数量少于20条,说明没有更多消息了
+ if (response.data.data.length < 20) {
+ this.hasMoreMessages = false;
+ }
} else {
this.$modal.msgWarning(response.msg || '该学生暂无对话记录');
}
@@ -287,6 +323,95 @@ export default {
this.dialogVisible = false;
this.currentStudent = null;
this.chatMessages = [];
+ // 重置分页状态
+ this.hasMoreMessages = true;
+ this.isLoadingMore = false;
+ this.loadMoreLoading = false;
+ },
+ /** 加载更多历史消息 */
+ loadMoreMessages() {
+ if (this.loadMoreLoading || !this.hasMoreMessages || this.chatMessages.length === 0) {
+ return;
+ }
+
+ this.loadMoreLoading = true;
+ this.isLoadingMore = true;
+
+ // 获取当前消息列表中最早的消息ID(created_at最小的)
+ const earliestMessage = this.chatMessages.reduce((earliest, current) => {
+ return current.created_at < earliest.created_at ? current : earliest;
+ });
+
+ getMessagesToAdmin({
+ user: this.currentStudent.stuNo,
+ limit: 50,
+ firstId: earliestMessage.id
+ }).then(response => {
+ console.log('加载更多消息API响应:', response);
+
+ if (response.code === 200 && response.data && response.data.data) {
+ const newMessages = response.data.data;
+
+ if (newMessages.length === 0) {
+ // 没有更多消息了
+ this.hasMoreMessages = false;
+ this.$message.info('已经到底了');
+ } else {
+ // 将新消息按时间排序后添加到现有消息列表的底部
+ const sortedNewMessages = newMessages.sort((a, b) => b.created_at - a.created_at);
+ this.chatMessages = [...this.chatMessages, ...sortedNewMessages];
+
+ // 如果返回的消息数量少于50条,说明没有更多消息了
+ if (newMessages.length < 50) {
+ this.hasMoreMessages = false;
+ }
+ }
+ } else {
+ this.$message.error('加载更多消息失败');
+ }
+
+ this.loadMoreLoading = false;
+ this.isLoadingMore = false;
+ }).catch(error => {
+ console.error('加载更多消息失败:', error);
+ this.$message.error('加载更多消息失败');
+ this.loadMoreLoading = false;
+ this.isLoadingMore = false;
+ });
+ },
+ /** 渲染Markdown内容 */
+ renderMarkdown(content) {
+ if (!content) return '';
+
+ // 配置marked选项
+ marked.setOptions({
+ breaks: true, // 支持换行
+ gfm: true, // 支持GitHub风格的markdown
+ sanitize: false // 允许HTML标签
+ });
+
+ // 自定义渲染器,为链接添加新标签页打开属性和内联样式
+ const renderer = new marked.Renderer();
+ renderer.link = function(href, title, text) {
+ const titleAttr = title ? ` title="${title}"` : '';
+ // 直接使用内联样式来确保样式生效,绕过CSS优先级问题
+ const inlineStyle = `
+ color: #1890ff !important;
+ background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(64, 158, 255, 0.1) 100%) !important;
+ border: 1px solid rgba(24, 144, 255, 0.3) !important;
+ border-radius: 4px !important;
+ padding: 2px 6px !important;
+ text-decoration: none !important;
+ display: inline-block !important;
+ margin: 0 2px !important;
+ transition: all 0.3s ease !important;
+ position: relative !important;
+ font-weight: 500 !important;
+ `;
+ return `${text}`;
+ };
+
+ return marked(content, { renderer });
}
}
};
@@ -582,4 +707,171 @@ export default {
.chat-content::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
+
+/* Markdown样式 */
+.ai-text h1, .ai-text h2, .ai-text h3, .ai-text h4, .ai-text h5, .ai-text h6 {
+ margin: 16px 0 8px 0;
+ font-weight: 600;
+ line-height: 1.4;
+}
+
+.ai-text h1 {
+ font-size: 1.5em;
+ color: #303133;
+ border-bottom: 2px solid #e4e7ed;
+ padding-bottom: 8px;
+}
+
+.ai-text h2 {
+ font-size: 1.3em;
+ color: #409eff;
+}
+
+.ai-text h3 {
+ font-size: 1.2em;
+ color: #606266;
+}
+
+.ai-text h4, .ai-text h5, .ai-text h6 {
+ font-size: 1.1em;
+ color: #909399;
+}
+
+.ai-text p {
+ margin: 8px 0;
+ line-height: 1.6;
+}
+
+.ai-text code {
+ background: #f5f5f5;
+ border: 1px solid #e4e7ed;
+ border-radius: 3px;
+ padding: 2px 6px;
+ font-family: 'Courier New', Consolas, monospace;
+ font-size: 0.9em;
+ color: #e74c3c;
+}
+
+.ai-text pre {
+ background: #2d3748;
+ color: #e2e8f0;
+ border-radius: 6px;
+ padding: 16px;
+ margin: 12px 0;
+ overflow-x: auto;
+ font-family: 'Courier New', Consolas, monospace;
+ font-size: 0.9em;
+ line-height: 1.4;
+}
+
+.ai-text pre code {
+ background: transparent;
+ border: none;
+ padding: 0;
+ color: inherit;
+ font-size: inherit;
+}
+
+.ai-text ul, .ai-text ol {
+ margin: 8px 0;
+ padding-left: 24px;
+}
+
+.ai-text li {
+ margin: 4px 0;
+ line-height: 1.6;
+}
+
+.ai-text ul li {
+ list-style-type: disc;
+}
+
+.ai-text ol li {
+ list-style-type: decimal;
+}
+
+.ai-text blockquote {
+ border-left: 4px solid #409eff;
+ background: #f8f9fa;
+ margin: 12px 0;
+ padding: 12px 16px;
+ color: #606266;
+ font-style: italic;
+}
+
+
+
+.ai-text table {
+ border-collapse: collapse;
+ width: 100%;
+ margin: 12px 0;
+}
+
+.ai-text th, .ai-text td {
+ border: 1px solid #e4e7ed;
+ padding: 8px 12px;
+ text-align: left;
+}
+
+.ai-text th {
+ background: #f5f7fa;
+ font-weight: 600;
+}
+
+.ai-text hr {
+ border: none;
+ border-top: 2px solid #e4e7ed;
+ margin: 16px 0;
+}
+
+.ai-text strong {
+ font-weight: 600;
+ color: #303133;
+}
+
+.ai-text em {
+ font-style: italic;
+ color: #606266;
+}
+
+/* 加载更多按钮样式 */
+.load-more-container {
+ text-align: center;
+ padding: 20px;
+ border-top: 1px solid #e4e7ed;
+ margin-top: 16px;
+}
+
+.load-more-btn {
+ background: linear-gradient(135deg, #409eff 0%, #1890ff 100%);
+ border: none;
+ border-radius: 6px;
+ padding: 8px 24px;
+ font-size: 14px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
+ transition: all 0.3s ease;
+}
+
+.load-more-btn:hover {
+ background: linear-gradient(135deg, #66b1ff 0%, #40a9ff 100%);
+ box-shadow: 0 4px 8px rgba(64, 158, 255, 0.4);
+ transform: translateY(-1px);
+}
+
+.no-more-messages {
+ margin: 16px 0;
+}
+
+.no-more-text {
+ color: #909399;
+ font-size: 14px;
+ font-style: italic;
+ padding: 0 16px;
+ background: #f8f9fa;
+}
+
+.el-divider--horizontal {
+ margin: 16px 0;
+}
\ No newline at end of file