diff --git a/src/layout/components/Aichat/ChatPopup.vue b/src/layout/components/Aichat/ChatPopup.vue index d305626..f35bae0 100644 --- a/src/layout/components/Aichat/ChatPopup.vue +++ b/src/layout/components/Aichat/ChatPopup.vue @@ -141,7 +141,10 @@ export default { showSingleReference: {}, // Markdown 渲染器 - md: md + md: md, + + // 组件销毁标志 + isDestroyed: false } }, computed: { @@ -160,6 +163,23 @@ export default { return this.name || localStorage.getItem('userName') || '用户' } }, + watch: { + // 监听聊天框显示状态变化 + '$parent.showAI'(newVal, oldVal) { + if (newVal && !oldVal && !this.isDestroyed) { + // 只在从关闭状态变为打开状态时,且组件未销毁时,确保滚动到底部 + this.$nextTick(() => { + if (!this.isDestroyed) { + setTimeout(() => { + if (!this.isDestroyed) { + this.scrollToBottom(false) + } + }, 100) + } + }) + } + } + }, mounted() { console.log(this.userInfo) // 使用store中的userInfo.userName作为学号 @@ -168,12 +188,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: { /** @@ -254,8 +293,8 @@ export default { // 滚动到底部 this.$nextTick(() => { setTimeout(() => { - this.scrollToBottom(true) // 使用平滑滚动 - }, 200) + this.scrollToBottom(false) // 初始化时直接定位到底部,不使用平滑滚动 + }, 100) // 减少延迟时间 }) } catch (error) { @@ -491,23 +530,37 @@ export default { scrollToBottom(smooth = false) { 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) // 非平滑滚动时更快更新 } }) }, @@ -689,17 +742,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); } /* 导航栏 */ @@ -710,7 +767,29 @@ 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 { @@ -721,17 +800,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); } /* 消息列表 */ @@ -740,7 +827,8 @@ export default { overflow-y: auto; padding: 20px; background: #f8f9fa; - /* 移除scroll-behavior,避免与手动滚动控制冲突 */ + /* 强制禁用平滑滚动,确保立即定位 */ + scroll-behavior: auto !important; /* 优化滚动条样式 */ scrollbar-width: thin; scrollbar-color: #c1c1c1 transparent; @@ -817,6 +905,23 @@ 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); + } } /* 用户消息 */ @@ -834,6 +939,29 @@ 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消息 */ @@ -845,12 +973,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); } /* 头像 */ @@ -1059,8 +1194,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 { @@ -1097,16 +1244,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); }