From 0c3fca167a7d9e22bfa40948765cf4cbcf489978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A6=85=E9=A5=BC?= <2815246336@qq.com> Date: Tue, 19 Aug 2025 12:05:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/components/Aichat/ChatPopup.vue | 125 ++++++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/src/layout/components/Aichat/ChatPopup.vue b/src/layout/components/Aichat/ChatPopup.vue index b0523de..1560e6a 100644 --- a/src/layout/components/Aichat/ChatPopup.vue +++ b/src/layout/components/Aichat/ChatPopup.vue @@ -219,6 +219,13 @@ export default { // 消息显示优化 maxVisibleMessages: 50, // 最大可见消息数量 messageRenderBatch: 20, // 每批渲染的消息数量 + + // 鼠标跟踪相关 + mouseX: 0, // 鼠标X坐标 + mouseY: 0, // 鼠标Y坐标 + eyeTrackingEnabled: true, // 是否启用眼睛跟踪 + avatarRect: null, // 头像位置信息 + eyeUpdateTimer: null, // 眼睛更新定时器 } }, computed: { @@ -255,6 +262,8 @@ export default { setTimeout(() => { if (!this.isDestroyed) { this.scrollToBottom(false) + // 更新头像位置信息,确保鼠标跟踪正常工作 + this.updateAvatarRect() } }, 100) } @@ -283,6 +292,9 @@ export default { // 启动数字人自动眨眼 this.startAutoBlinking() + // 启动鼠标跟踪 + this.startMouseTracking() + // 确保DOM完全渲染后再初始化聊天 this.$nextTick(() => { setTimeout(async () => { @@ -321,11 +333,18 @@ export default { clearTimeout(this.contentUpdateTimer) this.contentUpdateTimer = null } + if (this.eyeUpdateTimer) { + clearTimeout(this.eyeUpdateTimer) + this.eyeUpdateTimer = null + } // 清理数字人相关定时器 this.stopAutoBlinking() this.stopAIAnimation() + // 停止鼠标跟踪 + this.stopMouseTracking() + // 取消正在进行的请求 if (this.currentCancel) { this.currentCancel() @@ -745,6 +764,10 @@ export default { this.inputMessage = '' this.sending = true + // 初始化定时器变量 + let streamTimeout = null + let noDataTimeout = null + // 添加到请求队列 const requestId = Date.now() this.requestQueue.push(requestId) @@ -796,7 +819,7 @@ export default { }) // 添加额外的超时保护 - const streamTimeout = setTimeout(() => { + streamTimeout = setTimeout(() => { if (cancel) { cancel('流式响应超时') } @@ -811,7 +834,6 @@ export default { const { reader, decoder } = response let buffer = '' let lastUpdateTime = Date.now() - let noDataTimeout = null while (true) { // 设置无数据超时检测 @@ -1197,6 +1219,101 @@ export default { } }, + // 启动鼠标跟踪 + startMouseTracking() { + if (!this.eyeTrackingEnabled) return + + // 添加鼠标移动监听器 + document.addEventListener('mousemove', this.handleMouseMove) + + // 获取头像位置信息 + this.$nextTick(() => { + this.updateAvatarRect() + }) + + // 监听窗口大小变化,更新头像位置 + window.addEventListener('resize', this.updateAvatarRect) + }, + + // 停止鼠标跟踪 + stopMouseTracking() { + document.removeEventListener('mousemove', this.handleMouseMove) + window.removeEventListener('resize', this.updateAvatarRect) + + // 清理眼睛更新定时器 + if (this.eyeUpdateTimer) { + clearTimeout(this.eyeUpdateTimer) + this.eyeUpdateTimer = null + } + }, + + // 处理鼠标移动 + handleMouseMove(event) { + if (!this.eyeTrackingEnabled || this.isDestroyed) return + + this.mouseX = event.clientX + this.mouseY = event.clientY + + // 使用节流避免过于频繁的更新 + if (this.eyeUpdateTimer) { + clearTimeout(this.eyeUpdateTimer) + } + + this.eyeUpdateTimer = setTimeout(() => { + if (!this.isDestroyed) { + this.updateEyePosition() + } + }, 16) // 约60fps的更新频率 + }, + + // 更新头像位置信息 + updateAvatarRect() { + if (this.isDestroyed) return + + const avatarElement = this.$el.querySelector('.digital-avatar') + if (avatarElement) { + this.avatarRect = avatarElement.getBoundingClientRect() + } + }, + + // 更新眼睛位置 + updateEyePosition() { + if (!this.avatarRect || this.isDestroyed) return + + // 计算头像中心点 + const avatarCenterX = this.avatarRect.left + this.avatarRect.width / 2 + const avatarCenterY = this.avatarRect.top + this.avatarRect.height / 2 + + // 计算鼠标相对于头像中心的方向 + const deltaX = this.mouseX - avatarCenterX + const deltaY = this.mouseY - avatarCenterY + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + + // 避免除零错误 + if (distance === 0) return + + // 计算单位向量 + const unitX = deltaX / distance + const unitY = deltaY / distance + + // 根据距离调整眼睛移动幅度 + const maxMoveDistance = 1.5 // 最大移动距离(像素) + const sensitivity = Math.min(distance / 100, 1) // 距离越远,眼睛移动越明显 + + const moveX = unitX * maxMoveDistance * sensitivity + const moveY = unitY * maxMoveDistance * sensitivity + + // 应用眼睛位置 + const leftEye = this.$el.querySelector('.left-eye') + const rightEye = this.$el.querySelector('.right-eye') + + if (leftEye && rightEye) { + // 使用CSS变量来实现更平滑的动画 + leftEye.style.transform = `translate(${moveX}px, ${moveY}px)` + rightEye.style.transform = `translate(${moveX}px, ${moveY}px)` + } + }, + /** * 处理点赞 */ @@ -1724,7 +1841,9 @@ export default { height: 4px; background: #333; border-radius: 50%; - transition: all 0.15s ease; + transition: height 0.15s ease, background 0.15s ease, transform 0.1s ease-out; + position: relative; + transform-origin: center; } .eye.blink {