From 480b5f1a8f2e871abf421aec1a83ee63b9ac82fe Mon Sep 17 00:00:00 2001
From: Wangxin
Date: Mon, 10 Nov 2025 23:54:26 +0800
Subject: [PATCH 1/2] =?UTF-8?q?=E3=80=90web=E3=80=91=20ai=E5=8A=A9?=
=?UTF-8?q?=E6=89=8B=E8=B0=83=E6=95=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/views/QandA/Aiassistant/index.vue | 280 ++++++++++++++++--
1 file changed, 252 insertions(+), 28 deletions(-)
diff --git a/ALOps_sys_fe/alops-ui/src/views/QandA/Aiassistant/index.vue b/ALOps_sys_fe/alops-ui/src/views/QandA/Aiassistant/index.vue
index eaca675a..a5ec32da 100644
--- a/ALOps_sys_fe/alops-ui/src/views/QandA/Aiassistant/index.vue
+++ b/ALOps_sys_fe/alops-ui/src/views/QandA/Aiassistant/index.vue
@@ -18,8 +18,8 @@
:class="['message', message.sender]"
>
-
{{ message.text }}
-
{{ message.time }}
+
+
@@ -73,7 +73,7 @@ export default {
]
};
},mounted() {
- // 初始化时滚动到底部
+ // 初始化时滚动到底部,符合大模型聊天体验
this.scrollToBottom();
},
methods: {
@@ -86,33 +86,109 @@ export default {
sender: 'customer',
time: this.formatTime(new Date())
});
+ // 用户发送消息后滚动到底部,符合大模型聊天体验
+ this.scrollToBottom();
let newMessageContent = this.newMessage;
- // 模拟自动回复
- setTimeout(() => {
- this.autoReply(newMessageContent);
- }, 1000);
+ // 直接调用autoReply,不再延迟
+ this.autoReply(newMessageContent);
this.newMessage = '';
},
async autoReply(userMessage) {
console.log("%c 🇱🇺: autoReply -> userMessage ", "font-size:16px;background-color:#c70b31;color:white;", userMessage)
let replyText = '';
- let res = await knowledgeAsk({question: userMessage });
- if (res.code == 200) {
- replyText = res.data.answer;
- }else {
- replyText = '很抱歉,暂时无法回答您的问题。您可以尝试其他常见问题或联系客服人员。';
+
+ // 测试模式:当用户输入"测试数据"时,返回格式化的虚拟数据
+ if (userMessage.includes('测试数据')) {
+ // 模拟短暂延迟,让测试数据也有加载感觉
+ setTimeout(() => {
+ replyText = this.getTestData();
+ // 添加一个空的回复消息,稍后通过打字效果填充内容
+ const messageIndex = this.messages.length;
+ this.messages.push({
+ text: '', // 初始为空
+ sender: 'service',
+ time: this.formatTime(new Date())
+ });
+
+ // 调用打字效果方法
+ this.typeWriter(messageIndex, replyText);
+ }, 500);
+ } else {
+ // 设置loading状态
+ this.isLoading = true;
+
+ try {
+ let res = await knowledgeAsk({question: userMessage });
+ if (res.code == 200) {
+ replyText = this.formatResponse(res.data.answer);
+ } else {
+ replyText = '很抱歉,暂时无法回答您的问题。您可以尝试其他常见问题或联系客服人员。';
+ }
+
+ // 添加一个空的回复消息,稍后通过打字效果填充内容
+ const messageIndex = this.messages.length;
+ this.messages.push({
+ text: '', // 初始为空
+ sender: 'service',
+ time: this.formatTime(new Date())
+ });
+
+ // 调用打字效果方法
+ this.typeWriter(messageIndex, replyText);
+ } finally {
+ // 无论请求成功失败,都关闭loading
+ setTimeout(() => {
+ this.isLoading = false;
+ }, 300); // 延迟一点关闭,让用户有完成感
+ }
}
-
- // 添加客服回复
- this.messages.push({
- text: replyText,
- sender: 'service',
- time: this.formatTime(new Date())
+ },
+
+ // 打字效果方法
+ typeWriter(messageIndex, fullText) {
+ let currentIndex = 0;
+
+ // 优化滚动:先滚动到底部
+ this.$nextTick(() => {
+ this.scrollToBottom();
});
- // 滚动到底部
- this.scrollToBottom();
+ const typeInterval = setInterval(() => {
+ if (currentIndex < fullText.length) {
+ // 优化打字速度:根据文本长度动态调整速度
+ let typeSpeed = 15; // 加快速度到15毫秒/字符
+
+ // 快速处理HTML标签字符,避免标签被拆分导致的闪烁
+ if (fullText[currentIndex] === '<' || (currentIndex > 0 && fullText[currentIndex-1] === '<')) {
+ // 查找完整的HTML标签
+ const tagEndIndex = fullText.indexOf('>', currentIndex);
+ if (tagEndIndex !== -1) {
+ // 一次性添加整个标签
+ this.messages[messageIndex].text = fullText.substring(0, tagEndIndex + 1);
+ currentIndex = tagEndIndex + 1;
+ } else {
+ // 正常逐字添加
+ this.messages[messageIndex].text = fullText.substring(0, currentIndex + 1);
+ currentIndex++;
+ }
+ } else {
+ // 普通字符正常添加
+ this.messages[messageIndex].text = fullText.substring(0, currentIndex + 1);
+ currentIndex++;
+ }
+
+ // 优化滚动:减少滚动频率,避免抖动
+ if (currentIndex % 10 === 0 || currentIndex === fullText.length) {
+ this.scrollToBottom();
+ }
+ } else {
+ // 打字完成
+ clearInterval(typeInterval);
+ // 最终滚动到底部
+ this.scrollToBottom();
+ }
+ }, 15); // 加快打字速度到15毫秒/字符
},
selectFaq(faq) {
// 添加用户选择的常见问题
@@ -122,14 +198,16 @@ export default {
time: this.formatTime(new Date())
});
- // 添加对应的回答
+ // 添加对应的回答,使用打字效果
setTimeout(() => {
+ const messageIndex = this.messages.length;
this.messages.push({
- text: faq.answer,
+ text: '', // 初始为空
sender: 'service',
time: this.formatTime(new Date())
});
- this.scrollToBottom();
+ // 使用打字效果显示回答
+ this.typeWriter(messageIndex, faq.answer);
}, 1000);
},
scrollToBottom() {
@@ -138,6 +216,101 @@ export default {
container.scrollTop = container.scrollHeight;
});
},
+ // 滚动到顶部方法 - 保留但不自动调用
+ scrollToTop() {
+ this.$nextTick(() => {
+ const container = this.$refs.messagesContainer;
+ container.scrollTop = 0;
+ });
+ },
+ // 格式化返回值的方法 - 将Markdown转换为HTML
+ formatResponse(answer) {
+ if (!answer) return '';
+
+ // 简单的Markdown解析实现
+ return this.markdownToHtml(answer);
+ },
+ // 简单的Markdown转HTML函数
+ markdownToHtml(text) {
+ if (!text) return '';
+
+ // 转换标题
+ text = text.replace(/^### (.*$)/gm, '$1
');
+ text = text.replace(/^## (.*$)/gm, '$1
');
+ text = text.replace(/^# (.*$)/gm, '$1
');
+
+ // 转换分隔线
+ text = text.replace(/^---$/gm, '
');
+
+ // 转换加粗文本
+ text = text.replace(/\*\*(.*?)\*\*/g, '$1');
+
+ // 转换斜体文本
+ text = text.replace(/\*(.*?)\*/g, '$1');
+
+ // 转换引用块
+ text = text.replace(/^> (.*$)/gm, '$1
');
+
+ // 转换无序列表项
+ text = text.replace(/^- (.*$)/gm, '');
+ // 合并连续的列表项
+ text = text.replace(/<\/li><\/ul>\s*- /g, '
- ');
+
+ // 转换有序列表项
+ text = text.replace(/^(\d+)\. (.*$)/gm, '
- $2
');
+ // 合并连续的有序列表项
+ text = text.replace(/<\/li><\/ol>\s*- /g, '
- ');
+
+ // 转换段落
+ text = text.replace(/^(?!$1
');
+
+ return text;
+ },
+ // 获取测试数据
+ getTestData() {
+ // 直接返回HTML格式,避免解析错误
+ return `
+测试数据 - 课程安排信息
+
+
+1. 选课时间安排
+
+ - 预选时间:2025年8月26日9:00 — 8月27日16:00
+ - 正选时间:2025年8月29日9:00 — 8月30日16:00
+
+说明:此为2025-2026学年秋季学期的固定选课窗口期
+
+2. 选课方式与系统登录步骤
+
+ - 选课方式:学生必须通过电脑端登录"一网通办"平台
+ -
+ 系统登录三步法:
+
+ - 激活账号:完成账号激活
+ - 关注公众号:扫码关注
+ - 登录系统:进入平台进行选课
+
+
+
+
+3. 注意事项
+
+ -
+ 体育课规则:
+
+ - 公选课的体育课不计入体育必修课学分
+ - 学生毕业前须修4次体育课
+
+
+ -
+ 课程号前缀要求:
+
+
+
+`;
+ },
formatTime(date) {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
@@ -147,6 +320,7 @@ export default {
watch: {
messages: {
handler() {
+ // 当消息数组发生变化时,滚动到底部,确保最新消息可见
this.scrollToBottom();
},
deep: true
@@ -202,17 +376,28 @@ export default {
justify-content: flex-end;
}
.message-content {
- max-width: 70%;
padding: 10px 15px;
border-radius: 4px;
+ /* 固定最小高度,避免内容变化时高度抖动 */
+ min-height: 20px;
}
.message.customer .message-content {
- background-color: #ecf5ff;
- border: 1px solid #d9ecff;
+ background:#f5f5f5;
+ border-radius: 26px;
+ font-family: PingFang SC;
+ font-size: 16px;
+ font-weight: 400;
+ justify-self: end;
+ line-height: 28px;
+ max-width: 100%;
+ padding: 10px 20px;
+ white-space: pre-wrap;
+ word-break: break-all;
}
.message.service .message-content {
- background-color: #f4f4f5;
- border: 1px solid #e9e9eb;
+ background-color: transparent; /* 移除背景色 */
+ border: none; /* 移除边框 */
+ width: 100%; /* 设置宽度为100% */
}
.chat-input {
padding: 15px;
@@ -238,4 +423,43 @@ export default {
margin-top: 5px;
text-align: right;
}
+
+ /* Loading样式 */
+ .loading-indicator {
+ display: flex;
+ justify-content: flex-start;
+ padding: 10px 0;
+ }
+ .loading-dots {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ background-color: #f4f4f5;
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: 1px solid #e9e9eb;
+ }
+ .loading-dots .dot {
+ width: 8px;
+ height: 8px;
+ background-color: #409EFF;
+ border-radius: 50%;
+ animation: loading 1.4s infinite ease-in-out both;
+ }
+ .loading-dots .dot:nth-child(1) {
+ animation-delay: -0.32s;
+ }
+ .loading-dots .dot:nth-child(2) {
+ animation-delay: -0.16s;
+ }
+ @keyframes loading {
+ 0%, 80%, 100% {
+ transform: scale(0);
+ opacity: 0.5;
+ }
+ 40% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ }
\ No newline at end of file
From 140ede9177486ef56c4fb4f19832840afea2d096 Mon Sep 17 00:00:00 2001
From: Wangxin
Date: Tue, 11 Nov 2025 00:07:39 +0800
Subject: [PATCH 2/2] =?UTF-8?q?=E3=80=90web=E3=80=91=20=E4=BF=AE=E6=94=B9?=
=?UTF-8?q?=E8=AF=A6=E6=83=85=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ALOps_sys_fe/alops-ui/src/router/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ALOps_sys_fe/alops-ui/src/router/index.js b/ALOps_sys_fe/alops-ui/src/router/index.js
index 4c2cee88..db1f1585 100644
--- a/ALOps_sys_fe/alops-ui/src/router/index.js
+++ b/ALOps_sys_fe/alops-ui/src/router/index.js
@@ -96,7 +96,7 @@ export const dynamicRoutes = [
path: '/inMonitoring/devicesState-detail',
component: Layout,
hidden: true,
- permissions: ['inMonitoring:devicesState:edit'],
+ permissions: ['inMonitoring:devicesState:detail'],
children: [
{
path: 'detail/:id',