diff --git a/src/api/stuCQS/process-center/auditDetails.js b/src/api/stuCQS/process-center/auditDetails.js index 84dc0c1..b7ec607 100644 --- a/src/api/stuCQS/process-center/auditDetails.js +++ b/src/api/stuCQS/process-center/auditDetails.js @@ -139,3 +139,10 @@ export function delAuditDetails(id) { method: 'post' }) } +//撤销审核 +export function cancelAudit(id) { + return request({ + url: '/comprehensive/auditDetails/cancelAudit/'+id, + method: 'post' + }) +} diff --git a/src/layout/components/Aichat/ChatPopup.vue b/src/layout/components/Aichat/ChatPopup.vue index e4a032f..4618bf9 100644 --- a/src/layout/components/Aichat/ChatPopup.vue +++ b/src/layout/components/Aichat/ChatPopup.vue @@ -64,7 +64,7 @@ -
+
AI回答也可能会犯错。请核查重要信息。
{ + if (!this.isDestroyed) { + setTimeout(() => { + if (!this.isDestroyed) { + this.scrollToBottom(false) + } + }, 100) + } + }) + } + } + }, mounted() { console.log(this.userInfo) // 使用store中的userInfo.userName作为学号 @@ -176,12 +196,31 @@ export default { this.userStuNo = this.userInfo.userName } console.log('当前用户学号:', this.user) - this.initChat() + + // 确保DOM完全渲染后再初始化聊天 + this.$nextTick(() => { + setTimeout(async () => { + await this.initChat() + // 初始化完成后立即滚动到底部 + this.$nextTick(() => { + this.scrollToBottom(false) + }) + }, 50) + }) }, beforeDestroy() { + // 设置销毁标志 + this.isDestroyed = true + + // 清理定时器 if (this.loadDebounceTimer) { clearTimeout(this.loadDebounceTimer) } + + // 取消正在进行的请求 + if (this.currentCancel) { + this.currentCancel() + } }, methods: { /** @@ -222,7 +261,8 @@ export default { conversationId: msg.conversation_id, created_at: msg.created_at, feedback: msg.feedback || null, - retrieverResources: msg.retriever_resources || [] + retrieverResources: msg.retriever_resources || [], + streamCompleted: true // 历史消息的流输出已完成 }) } }) @@ -262,8 +302,8 @@ export default { // 滚动到底部 this.$nextTick(() => { setTimeout(() => { - this.scrollToBottom(true) // 使用平滑滚动 - }, 200) + this.scrollToBottom(false) // 初始化时直接定位到底部,不使用平滑滚动 + }, 100) // 减少延迟时间 }) } catch (error) { @@ -418,7 +458,8 @@ export default { content: '正在思考...', messageId: 'ai-' + Date.now(), feedback: null, - retrieverResources: [] + retrieverResources: [], + streamCompleted: false // 流输出完成标志 } this.messages.push(aiMsg) @@ -477,6 +518,8 @@ export default { if (data.retriever_resources) { aiMsg.retrieverResources = data.retriever_resources } + // 标记流输出完成 + aiMsg.streamCompleted = true } } catch (e) { console.warn('JSON解析失败:', line, e) @@ -486,6 +529,7 @@ export default { } catch (error) { console.error('发送消息失败:', error) aiMsg.content = '抱歉,发送消息时出现错误,请稍后重试。' + aiMsg.streamCompleted = true // 即使出错也标记为完成,显示操作区域 this.showToast('发送消息失败') } finally { this.sending = false @@ -500,22 +544,37 @@ export default { this.$nextTick(() => { if (this.$refs.messageList) { const targetTop = this.$refs.messageList.scrollHeight + const messageList = this.$refs.messageList - if (smooth && this.$refs.messageList.scrollTo && !this.isLoadingHistory) { + if (smooth && messageList.scrollTo && !this.isLoadingHistory) { // 只在非加载状态时使用平滑滚动 - this.$refs.messageList.scrollTo({ - top: targetTop, + messageList.scrollTo({ + top: messageList.scrollHeight, behavior: 'smooth' }) } else { - // 直接设置滚动位置 - this.$refs.messageList.scrollTop = targetTop + // 强制立即滚动到底部 + const forceScrollToBottom = () => { + messageList.scrollTop = messageList.scrollHeight + } + + // 立即执行一次 + forceScrollToBottom() + + // 再次确保滚动到底部 + this.$nextTick(() => { + forceScrollToBottom() + // 第三次确保 + setTimeout(() => { + forceScrollToBottom() + }, 10) + }) } // 延迟更新lastScrollTop,避免干扰滚动检测 setTimeout(() => { - this.lastScrollTop = targetTop - }, 100) + this.lastScrollTop = this.$refs.messageList.scrollHeight + }, smooth ? 300 : 50) // 非平滑滚动时更快更新 } }) }, @@ -719,17 +778,21 @@ export default { /* 聊天弹窗容器 */ .chat-popup { position: fixed; - bottom: 80px; - right: 20px; + bottom: 45px; + right: 60px; width: 400px; height: 600px; background: #fff; - border-radius: 12px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 8px 32px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; z-index: 1000; overflow: hidden; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + transform-origin: bottom right; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* 导航栏 */ @@ -740,7 +803,30 @@ export default { padding: 15px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; - border-radius: 12px 12px 0 0; + border-radius: 16px 16px 0 0; + position: relative; + overflow: hidden; +} + +.navbar::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + animation: shimmer 3s infinite; +} + +@keyframes shimmer { + 0% { + left: -100%; + } + + 100% { + left: 100%; + } } .nav-title { @@ -751,17 +837,25 @@ export default { .nav-close { font-size: 24px; cursor: pointer; - width: 30px; - height: 30px; + width: 40px; + height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 50%; - transition: background-color 0.2s; + transition: all 0.2s ease; + position: relative; + z-index: 10; } .nav-close:hover { background-color: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} + +.nav-close:active { + transform: scale(0.95); + background-color: rgba(255, 255, 255, 0.3); } /* 消息列表 */ @@ -770,7 +864,8 @@ export default { overflow-y: auto; padding: 20px; background: #f8f9fa; - /* 移除scroll-behavior,避免与手动滚动控制冲突 */ + /* 强制禁用平滑滚动,确保立即定位 */ + scroll-behavior: auto !important; /* 优化滚动条样式 */ scrollbar-width: thin; scrollbar-color: #c1c1c1 transparent; @@ -859,6 +954,25 @@ export default { /* 消息项 */ .message-item { margin-bottom: 20px; + animation: messageSlideIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + transform-origin: left center; +} + +@keyframes messageSlideIn { + 0% { + opacity: 0; + transform: translateY(20px) scale(0.95); + } + + 60% { + opacity: 0.8; + transform: translateY(-2px) scale(1.01); + } + + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } } /* 用户消息 */ @@ -876,6 +990,30 @@ export default { border-radius: 18px 18px 4px 18px; max-width: 70%; word-wrap: break-word; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); + position: relative; + overflow: hidden; +} + +.user-message .message-content::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); + animation: messageShimmer 2s infinite; +} + +@keyframes messageShimmer { + 0% { + left: -100%; + } + + 100% { + left: 100%; + } } /* AI消息 */ @@ -887,12 +1025,19 @@ export default { } .ai-message .message-content { - background: white; + background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%); border: 1px solid #e1e5e9; padding: 12px 16px; border-radius: 18px 18px 18px 4px; max-width: 70%; word-wrap: break-word; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.06); + transition: all 0.2s ease; +} + +.ai-message .message-content:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.08); + transform: translateY(-1px); } /* 头像 */ @@ -1101,8 +1246,20 @@ export default { /* 输入框区域 */ .input-area { padding: 20px; - background: white; + background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%); border-top: 1px solid #e1e5e9; + backdrop-filter: blur(10px); + position: relative; +} + +.input-area::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.3), transparent); } .input-container { @@ -1139,16 +1296,42 @@ export default { cursor: pointer; font-size: 14px; font-weight: 500; - transition: opacity 0.2s; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); + position: relative; + overflow: hidden; +} + +.send-button::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s; } .send-button:hover:not(:disabled) { - opacity: 0.9; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); +} + +.send-button:hover:not(:disabled)::before { + left: 100%; +} + +.send-button:active:not(:disabled) { + transform: translateY(0); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } .send-button:disabled { opacity: 0.5; cursor: not-allowed; + transform: none; + box-shadow: none; } /* Markdown 内容样式 */ diff --git a/src/layout/index.vue b/src/layout/index.vue index 17e87ae..336810e 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -20,12 +20,14 @@
-
+
AI
- + + +
@@ -94,11 +96,12 @@ export default { }, // 切换AI弹窗显示状态 toggleAI() { - this.showAI = !this.showAI - // 如果需要在显示时执行原有逻辑,可以取消下面的注释 - // if (this.showAI) { - // this.initializeAI() - // } + // 使用明确的状态切换,避免与close事件冲突 + if (this.showAI) { + this.showAI = false + } else { + this.showAI = true + } }, // 原有AI初始化逻辑,保持注释状态 async initializeAI() { @@ -222,5 +225,41 @@ export default { color: #fff; cursor: pointer; z-index: 9999; + transition: all 0.2s ease; + box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); +} + +.ai-hover:hover { + transform: scale(1.05); + box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4); +} + +/* 聊天弹窗动画 - 优化版本,避免闪烁 */ +.chat-popup-enter-active { + transition: all 0.25s ease-out; +} + +.chat-popup-leave-active { + transition: all 0.15s ease-in; +} + +.chat-popup-enter-from { + opacity: 0; + transform: translateY(15px) scale(0.95); +} + +.chat-popup-enter-to { + opacity: 1; + transform: translateY(0) scale(1); +} + +.chat-popup-leave-from { + opacity: 1; + transform: translateY(0) scale(1); +} + +.chat-popup-leave-to { + opacity: 0; + transform: translateY(15px) scale(0.95); } diff --git a/src/views/aitutor/chathistory/index.vue b/src/views/aitutor/chathistory/index.vue index 42c9e8c..2222a3d 100644 --- a/src/views/aitutor/chathistory/index.vue +++ b/src/views/aitutor/chathistory/index.vue @@ -54,7 +54,6 @@
- @@ -114,14 +113,6 @@
{{ parseTime(message.created_at * 1000, '{y}-{m}-{d} {h}:{i}:{s}') }} -
@@ -132,10 +123,25 @@
AI回答:
-
+
+
+ + + +
@@ -170,7 +176,6 @@ 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", dicts: ['srs_stu_status'], @@ -412,7 +417,8 @@ export default { }; return marked(content, { renderer }); - } + }, + } }; @@ -874,4 +880,41 @@ export default { .el-divider--horizontal { margin: 16px 0; } + + + +/* AI消息容器样式 */ +.ai-message-container { + position: relative; +} + +/* 反馈图标样式 */ +.feedback-icon { + position: absolute; + bottom: 8px; + right: 8px; + z-index: 10; +} +.feedback-like-icon { + width: 16px; + height: 16px; + opacity: 0.8; + transition: all 0.3s ease; +} +.feedback-like-icon:hover { + opacity: 1; + transform: scale(1.1); +} + +.feedback-dislike-icon { + width: 16px; + height: 16px; + opacity: 0.8; + transition: all 0.3s ease; +} + +.feedback-dislike-icon:hover { + opacity: 1; + transform: scale(1.1); +} \ No newline at end of file diff --git a/src/views/stuCQS/flowable/processed/index.vue b/src/views/stuCQS/flowable/processed/index.vue index 5c7ca3a..921bf8f 100644 --- a/src/views/stuCQS/flowable/processed/index.vue +++ b/src/views/stuCQS/flowable/processed/index.vue @@ -32,6 +32,8 @@ - + - \ No newline at end of file + \ No newline at end of file