@@ -161,6 +181,13 @@ export default {
// 组件销毁标志
isDestroyed: false,
+ // 性能优化相关
+ scrollThrottleTimer: null, // 滚动节流定时器
+ contentUpdateTimer: null, // 内容更新节流定时器
+ isUserAtBottom: true, // 用户是否在底部
+ lastContentLength: 0, // 上次内容长度
+ scrollPending: false, // 滚动待处理标志
+
// 性能优化和错误边界
requestQueue: [],
maxRetries: 2,
@@ -168,11 +195,37 @@ export default {
performanceMonitor: null,
memoryCheckInterval: null,
lastMemoryUsage: 0,
-
+
// 请求监控
activeRequests: new Map(), // 存储活跃请求的信息
+
+ // 数字人相关状态
+ isAISpeaking: false, // AI是否在说话
+ isAIThinking: false, // AI是否在思考
+ isBlinking: false, // 是否在眨眼
+ blinkTimer: null, // 眨眼定时器
+ speakingTimer: null, // 说话动画定时器
requestMonitorInterval: null,
- maxRequestDuration: 60000 // 最大请求持续时间60秒
+ maxRequestDuration: 60000, // 最大请求持续时间60秒
+
+ // Markdown渲染缓存
+ markdownCache: new Map(), // 缓存已渲染的Markdown内容
+ maxCacheSize: 100, // 最大缓存条目数
+
+ // 引用资源分组缓存
+ referencesCache: new Map(), // 缓存引用资源分组结果
+ maxReferencesCacheSize: 50, // 最大引用缓存条目数
+
+ // 消息显示优化
+ maxVisibleMessages: 50, // 最大可见消息数量
+ messageRenderBatch: 20, // 每批渲染的消息数量
+
+ // 鼠标跟踪相关
+ mouseX: 0, // 鼠标X坐标
+ mouseY: 0, // 鼠标Y坐标
+ eyeTrackingEnabled: true, // 是否启用眼睛跟踪
+ avatarRect: null, // 头像位置信息
+ eyeUpdateTimer: null, // 眼睛更新定时器
}
},
computed: {
@@ -189,6 +242,14 @@ export default {
// 获取用户名
userName() {
return this.name || localStorage.getItem('userName') || '用户'
+ },
+ // 获取可见消息列表(性能优化)
+ visibleMessages() {
+ if (this.messages.length <= this.maxVisibleMessages) {
+ return this.messages
+ }
+ // 只显示最新的消息,保持对话连续性
+ return this.messages.slice(-this.maxVisibleMessages)
}
},
watch: {
@@ -200,9 +261,11 @@ export default {
if (!this.isDestroyed) {
setTimeout(() => {
if (!this.isDestroyed) {
- this.scrollToBottom(false)
- }
- }, 100)
+ this.scrollToBottom(false)
+ // 更新头像位置信息,确保鼠标跟踪正常工作
+ this.updateAvatarRect()
+ }
+ }, 100)
}
})
}
@@ -222,10 +285,16 @@ export default {
// 启动性能监控
this.startPerformanceMonitoring()
-
+
// 启动请求监控
this.startRequestMonitoring()
+ // 启动数字人自动眨眼
+ this.startAutoBlinking()
+
+ // 启动鼠标跟踪
+ this.startMouseTracking()
+
// 确保DOM完全渲染后再初始化聊天
this.$nextTick(() => {
setTimeout(async () => {
@@ -240,38 +309,63 @@ export default {
beforeDestroy() {
// 设置销毁标志
this.isDestroyed = true
-
+
// 移除全局错误处理器
window.removeEventListener('unhandledrejection', this.handleUnhandledRejection)
window.removeEventListener('error', this.handleGlobalError)
-
+
// 停止性能监控
this.stopPerformanceMonitoring()
-
+
// 停止请求监控
this.stopRequestMonitoring()
-
+
// 清理定时器
if (this.loadDebounceTimer) {
clearTimeout(this.loadDebounceTimer)
this.loadDebounceTimer = null
}
-
+ if (this.scrollThrottleTimer) {
+ clearTimeout(this.scrollThrottleTimer)
+ this.scrollThrottleTimer = null
+ }
+ if (this.contentUpdateTimer) {
+ 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()
this.currentCancel = null
}
-
+
// 清理请求队列
this.requestQueue = []
-
+
// 清理消息数据
this.messages = []
-
+
// 清理引用展示状态
this.showSingleReference = {}
-
+
+ // 清理markdown缓存
+ this.markdownCache.clear()
+
+ // 清理引用资源分组缓存
+ this.referencesCache.clear()
+
// 重置所有状态
this.conversation_id = ''
this.earliestMessageId = null
@@ -280,7 +374,7 @@ export default {
this.isLoadingHistory = false
this.hasMoreHistory = false
this.initFailed = false
-
+
// 强制垃圾回收(如果浏览器支持)
if (window.gc) {
try {
@@ -299,25 +393,25 @@ export default {
if (this.isLoadingHistory || this.isDestroyed) {
return
}
-
+
try {
this.isLoadingHistory = true
this.retryCount = 0
-
+
// 添加到请求队列
const requestId = Date.now()
this.requestQueue.push(requestId)
-
+
// 如果队列中有太多请求,清理旧请求
if (this.requestQueue.length > 3) {
this.requestQueue = this.requestQueue.slice(-3)
}
-
+
// 添加超时控制,避免卡死
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), 8000) // 减少到8秒
})
-
+
// 创建取消Promise
const cancelPromise = new Promise((_, reject) => {
const checkCancel = () => {
@@ -338,22 +432,24 @@ export default {
// 使用Promise.race来实现超时和取消控制
const res = await Promise.race([historyPromise, timeoutPromise, cancelPromise])
-
+
// 从队列中移除当前请求
this.requestQueue = this.requestQueue.filter(id => id !== requestId)
-
+ // console.log('历史记录响应:', res);
if (this.isDestroyed) {
return
}
if (res.code === 200 && res.data && Array.isArray(res.data.data)) {
const newMessages = []
+ // console.log('历史记录:', res.data.data)
- // 处理历史消息
+ // 批量处理历史消息,减少DOM操作
+ const messagesBatch = []
res.data.data.forEach(msg => {
// 用户消息
if (msg.query) {
- newMessages.push({
+ messagesBatch.push({
sender: 'user',
avatar: require('@/assets/ai/yonghu.png'),
content: msg.query,
@@ -363,9 +459,10 @@ export default {
})
}
- // AI消息
+ // AI消息 - 延迟计算groupedReferences
if (msg.answer) {
- newMessages.push({
+ const retrieverResources = msg.retriever_resources || []
+ const aiMessage = {
sender: 'ai',
avatar: require('@/assets/ai/AI.png'),
content: msg.answer,
@@ -373,18 +470,32 @@ export default {
conversationId: msg.conversation_id,
created_at: msg.created_at,
feedback: msg.feedback || null,
- retrieverResources: msg.retriever_resources || [],
+ retrieverResources: retrieverResources,
streamCompleted: true // 历史消息的流输出已完成
- })
+ }
+
+ // 只有在有引用资源时才预计算分组
+ if (retrieverResources && retrieverResources.length > 0) {
+ // 使用异步方式计算,避免阻塞主线程
+ this.$nextTick(() => {
+ aiMessage.groupedReferences = this.getGroupedReferences(retrieverResources)
+ })
+ }
+
+ messagesBatch.push(aiMessage)
}
})
// 按时间排序(从旧到新)
- newMessages.sort((a, b) => {
+ messagesBatch.sort((a, b) => {
return a.created_at - b.created_at
})
- this.messages = newMessages
+ newMessages.push(...messagesBatch)
+ // console.log('处理后的历史记录:', newMessages)
+
+ // 分批渲染消息,避免一次性渲染过多消息导致卡死
+ this.renderMessagesInBatches(newMessages)
if (newMessages.length > 0) {
this.conversation_id = newMessages[0].conversationId
@@ -398,28 +509,30 @@ export default {
this.earliestMessageId = newMessages[0].messageId.replace('ai-', '')
}
}
-
+ // console.log('历史记录:', newMessages)
this.hasMoreHistory = res.data.has_more || false
} else {
+ // console.log('没有历史记录')
// 没有历史记录,显示欢迎消息
- this.messages = [{
+ const welcomeMessages = [{
sender: 'ai',
avatar: require('@/assets/ai/AI.png'),
content: '你好!我是智水AI辅导员,有什么可以帮助你的吗?',
messageId: 'welcome-' + Date.now()
}]
+ this.renderMessagesInBatches(welcomeMessages)
this.hasMoreHistory = false
}
this.initFailed = false
} catch (error) {
console.error('初始化聊天失败:', error)
-
+
// 如果是取消错误,直接返回
if (error.message === '请求已取消' || this.isDestroyed) {
return
}
-
+
// 根据错误类型显示不同的提示信息
let errorMessage = '加载历史记录失败'
if (error.message === '请求超时') {
@@ -431,17 +544,18 @@ export default {
} else if (error.response && error.response.status >= 500) {
errorMessage = '服务器暂时不可用,请稍后重试'
}
-
+
this.showToast(errorMessage)
-
+
// 显示欢迎消息作为降级方案
- this.messages = [{
+ const fallbackMessages = [{
sender: 'ai',
avatar: require('@/assets/ai/AI.png'),
content: '你好!我是智水AI辅导员,有什么可以帮助你的吗?\n\n如果遇到网络问题,请稍后重试或联系管理员。',
messageId: 'welcome-' + Date.now()
}]
-
+ this.renderMessagesInBatches(fallbackMessages)
+
// 重置相关状态
this.hasMoreHistory = false
this.conversation_id = ''
@@ -449,10 +563,10 @@ export default {
this.initFailed = true // 标记初始化失败
} finally {
this.isLoadingHistory = false
-
+
// 清理请求队列
this.requestQueue = []
-
+
// 滚动到底部
if (!this.isDestroyed) {
this.$nextTick(() => {
@@ -516,6 +630,7 @@ export default {
// AI消息
if (msg.answer) {
+ const retrieverResources = msg.retriever_resources || []
newMessages.push({
sender: 'ai',
avatar: require('@/assets/ai/AI.png'),
@@ -524,7 +639,8 @@ export default {
conversationId: msg.conversation_id,
created_at: msg.created_at,
feedback: msg.feedback || null,
- retrieverResources: msg.retriever_resources || []
+ retrieverResources: retrieverResources,
+ groupedReferences: this.getGroupedReferences(retrieverResources) // 预计算分组引用
})
}
})
@@ -578,7 +694,7 @@ export default {
}
} catch (error) {
console.error('加载历史记录失败:', error)
-
+
// 根据错误类型显示不同的提示信息
let errorMessage = '加载历史记录失败'
if (error.message === '加载历史记录超时') {
@@ -588,9 +704,9 @@ export default {
} else if (error.response && error.response.status >= 500) {
errorMessage = '服务器繁忙,请稍后重试'
}
-
+
this.showToast(errorMessage)
-
+
// 如果是超时或网络错误,不改变hasMoreHistory状态,允许用户重试
if (!error.message || (!error.message.includes('超时') && !error.message.includes('Network'))) {
this.hasMoreHistory = false
@@ -608,20 +724,20 @@ export default {
if (this.isLoadingHistory || this.isDestroyed) {
return
}
-
+
// 检查重试次数
if (this.retryCount >= this.maxRetries) {
this.showToast('重试次数过多,请刷新页面')
return
}
-
+
this.initFailed = false
this.retryCount++
-
+
// 清理之前的状态
this.messages = []
this.requestQueue = []
-
+
try {
await this.initChat()
} catch (error) {
@@ -637,7 +753,7 @@ export default {
if (!this.inputMessage.trim() || this.sending || this.isDestroyed) {
return
}
-
+
// 防止频繁发送
if (this.requestQueue.length > 2) {
this.showToast('请等待当前消息处理完成')
@@ -647,7 +763,11 @@ export default {
const userMessage = this.inputMessage.trim()
this.inputMessage = ''
this.sending = true
-
+
+ // 初始化定时器变量
+ let streamTimeout = null
+ let noDataTimeout = null
+
// 添加到请求队列
const requestId = Date.now()
this.requestQueue.push(requestId)
@@ -673,6 +793,9 @@ export default {
}
this.messages.push(aiMsg)
+ // 启动AI思考动画
+ this.startAIThinking()
+
this.scrollToBottom(true) // 添加消息后使用平滑滚动
try {
@@ -686,7 +809,7 @@ export default {
})
this.currentCancel = cancel
-
+
// 注册请求到监控系统
this.activeRequests.set(requestId, {
startTime: Date.now(),
@@ -694,9 +817,9 @@ export default {
cancel: cancel,
userMessage: userMessage
})
-
+
// 添加额外的超时保护
- const streamTimeout = setTimeout(() => {
+ streamTimeout = setTimeout(() => {
if (cancel) {
cancel('流式响应超时')
}
@@ -704,14 +827,13 @@ export default {
aiMsg.streamCompleted = true
this.sending = false
}, 45000) // 45秒超时保护
-
+
const response = await stream
clearTimeout(streamTimeout) // 清除超时保护
-
+
const { reader, decoder } = response
let buffer = ''
let lastUpdateTime = Date.now()
- let noDataTimeout = null
while (true) {
// 设置无数据超时检测
@@ -725,17 +847,17 @@ export default {
aiMsg.content = aiMsg.content === '正在思考...' ? '服务器响应中断,请重新发送' : aiMsg.content + '\n\n[响应中断]'
aiMsg.streamCompleted = true
}, 15000) // 15秒无数据则认为中断
-
+
const { done, value } = await reader.read()
-
+
// 清除无数据超时
if (noDataTimeout) {
clearTimeout(noDataTimeout)
noDataTimeout = null
}
-
+
if (done) break
-
+
// 更新最后接收数据时间
lastUpdateTime = Date.now()
@@ -754,8 +876,14 @@ export default {
if (data.event === 'message' && data.answer) {
const currentContent = aiMsg.content
const newContent = (currentContent === '正在思考...' ? '' : currentContent) + data.answer
- aiMsg.content = newContent
- this.scrollToBottom(true) // 流式响应时使用平滑滚动
+
+ // 如果是第一次收到回复,启动说话动画
+ if (currentContent === '正在思考...') {
+ this.startAISpeaking()
+ }
+
+ // 使用节流更新方法,减少频繁的DOM更新和滚动
+ this.throttledContentUpdate(aiMsg, newContent)
}
// 处理结束消息
@@ -770,13 +898,23 @@ export default {
userMsg.messageId = data.message_id
aiMsg.messageId = 'ai-' + data.message_id
}
- if (data.retriever_resources) {
- aiMsg.retrieverResources = data.retriever_resources
+ if (data.metadata && data.metadata.retriever_resources) {
+ // 使用Vue的响应式方法确保引用信息正确更新
+ this.$set(aiMsg, 'retrieverResources', data.metadata.retriever_resources)
+ this.$set(aiMsg, 'groupedReferences', this.getGroupedReferences(data.metadata.retriever_resources))
}
- // 标记流输出完成
+ // 标记流输出完成并触发最终滚动
aiMsg.streamCompleted = true
+ // 重置内容长度计数器
+ this.lastContentLength = 0
+ // 停止AI动画
+ this.stopAIAnimation()
+ // 流式响应结束后强制滚动到底部
+ this.$nextTick(() => {
+ this.scrollToBottom(true, true) // 使用平滑滚动并强制执行
+ })
}
-
+
// 处理错误事件
if (data.event === 'error') {
aiMsg.content = data.message || '服务器处理出错,请重新发送'
@@ -790,11 +928,11 @@ export default {
}
} catch (error) {
console.error('发送消息失败:', error)
-
+
// 清理所有定时器
if (streamTimeout) clearTimeout(streamTimeout)
if (noDataTimeout) clearTimeout(noDataTimeout)
-
+
// 根据错误类型显示不同的错误信息
let errorMessage = '发送消息失败'
if (error.message.includes('超时')) {
@@ -812,67 +950,113 @@ export default {
} else {
aiMsg.content = '抱歉,发送消息时出现错误,请稍后重试'
}
-
+
aiMsg.streamCompleted = true // 即使出错也标记为完成,显示操作区域
this.showToast(errorMessage, 'error')
} finally {
this.sending = false
this.currentCancel = null
-
+
// 清理所有定时器
if (streamTimeout) clearTimeout(streamTimeout)
if (noDataTimeout) clearTimeout(noDataTimeout)
-
+
// 从请求队列中移除当前请求
this.requestQueue = this.requestQueue.filter(id => id !== requestId)
-
+
// 从监控系统中移除请求
this.activeRequests.delete(requestId)
-
+
this.scrollToBottom(true) // 发送完成后使用平滑滚动
}
},
/**
- * 滚动到底部
+ * 分批渲染消息,避免一次性渲染过多消息导致卡死
*/
- scrollToBottom(smooth = false) {
- this.$nextTick(() => {
- if (this.$refs.messageList) {
- const targetTop = this.$refs.messageList.scrollHeight
- const messageList = this.$refs.messageList
+ renderMessagesInBatches(newMessages) {
+ if (!newMessages || newMessages.length === 0) {
+ this.messages = []
+ return
+ }
- if (smooth && messageList.scrollTo && !this.isLoadingHistory) {
- // 只在非加载状态时使用平滑滚动
- messageList.scrollTo({
- top: messageList.scrollHeight,
- behavior: 'smooth'
- })
- } else {
- // 强制立即滚动到底部
- const forceScrollToBottom = () => {
- messageList.scrollTop = messageList.scrollHeight
+ // 如果消息数量较少,直接渲染
+ if (newMessages.length <= this.messageRenderBatch) {
+ this.messages = newMessages
+ return
+ }
+
+ // 分批渲染
+ this.messages = []
+ let currentIndex = 0
+
+ const renderNextBatch = () => {
+ if (this.isDestroyed || currentIndex >= newMessages.length) {
+ return
+ }
+
+ const endIndex = Math.min(currentIndex + this.messageRenderBatch, newMessages.length)
+ const batch = newMessages.slice(currentIndex, endIndex)
+
+ // 添加当前批次的消息
+ this.messages.push(...batch)
+ currentIndex = endIndex
+
+ // 如果还有更多消息,继续渲染下一批
+ if (currentIndex < newMessages.length) {
+ this.$nextTick(() => {
+ setTimeout(renderNextBatch, 10) // 10ms延迟,让浏览器有时间处理当前批次
+ })
+ }
+ }
+
+ // 开始渲染第一批
+ renderNextBatch()
+ },
+
+ /**
+ * 滚动到底部(优化版本)
+ */
+ scrollToBottom(smooth = false, force = false) {
+ // 如果用户不在底部且不是强制滚动,则跳过
+ if (!force && !this.isUserAtBottom) {
+ return
+ }
+
+ // 节流处理,避免频繁滚动
+ if (this.scrollThrottleTimer) {
+ clearTimeout(this.scrollThrottleTimer)
+ }
+
+ this.scrollThrottleTimer = setTimeout(() => {
+ this.$nextTick(() => {
+ if (this.$refs.messageList && !this.isDestroyed) {
+ const messageList = this.$refs.messageList
+ const scrollHeight = messageList.scrollHeight
+
+ if (smooth && messageList.scrollTo && !this.isLoadingHistory) {
+ // 只在非加载状态时使用平滑滚动
+ messageList.scrollTo({
+ top: scrollHeight,
+ behavior: 'smooth'
+ })
+ } else {
+ // 立即滚动到底部
+ messageList.scrollTop = scrollHeight
}
- // 立即执行一次
- forceScrollToBottom()
-
- // 再次确保滚动到底部
- this.$nextTick(() => {
- forceScrollToBottom()
- // 第三次确保
- setTimeout(() => {
- forceScrollToBottom()
- }, 10)
- })
+ // 更新用户位置状态
+ this.isUserAtBottom = true
+
+ // 延迟更新lastScrollTop
+ setTimeout(() => {
+ if (!this.isDestroyed) {
+ this.lastScrollTop = scrollHeight
+ }
+ }, smooth ? 300 : 50)
}
-
- // 延迟更新lastScrollTop,避免干扰滚动检测
- setTimeout(() => {
- this.lastScrollTop = this.$refs.messageList.scrollHeight
- }, smooth ? 300 : 50) // 非平滑滚动时更快更新
- }
- })
+ })
+ }, smooth ? 0 : 16) // 非平滑滚动时使用16ms节流(约60fps)
},
/**
@@ -880,6 +1064,11 @@ export default {
*/
onScroll(e) {
const scrollTop = e.target.scrollTop
+ const scrollHeight = e.target.scrollHeight
+ const clientHeight = e.target.clientHeight
+
+ // 检测用户是否在底部(允许10px的误差)
+ this.isUserAtBottom = (scrollTop + clientHeight >= scrollHeight - 10)
// 检查是否需要加载历史记录
// 当滚动到距离顶部阈值范围内且向上滚动时触发加载
@@ -911,12 +1100,218 @@ export default {
},
/**
- * 渲染Markdown内容
+ * 渲染Markdown内容(带缓存优化)
*/
renderMarkdown(text) {
if (!text) return ''
+
+ // 检查缓存
+ if (this.markdownCache.has(text)) {
+ return this.markdownCache.get(text)
+ }
+
+ // 渲染markdown
const html = this.md.render(text)
- return DOMPurify.sanitize(html)
+ const sanitizedHtml = DOMPurify.sanitize(html)
+
+ // 缓存管理:如果缓存超过最大大小,删除最旧的条目
+ if (this.markdownCache.size >= this.maxCacheSize) {
+ const firstKey = this.markdownCache.keys().next().value
+ this.markdownCache.delete(firstKey)
+ }
+
+ // 添加到缓存
+ this.markdownCache.set(text, sanitizedHtml)
+
+ return sanitizedHtml
+ },
+
+ /**
+ * 节流更新内容
+ */
+ throttledContentUpdate(aiMsg, newContent) {
+ // 检查内容是否真的有变化
+ if (aiMsg.content === newContent) {
+ return
+ }
+
+ // 立即更新内容(保证响应性)
+ aiMsg.content = newContent
+
+ // 只有当内容长度显著增加时才滚动
+ const contentLengthDiff = newContent.length - this.lastContentLength
+ if (contentLengthDiff > 20) { // 内容增加超过20个字符才滚动
+ this.lastContentLength = newContent.length
+
+ // 清除之前的定时器
+ if (this.contentUpdateTimer) {
+ clearTimeout(this.contentUpdateTimer)
+ }
+
+ // 节流滚动更新
+ this.contentUpdateTimer = setTimeout(() => {
+ if (this.isUserAtBottom && !this.isDestroyed) {
+ this.scrollToBottom(false)
+ }
+ }, 100) // 100ms节流,减少滚动频率
+ }
+ },
+
+ /**
+ * 数字人控制方法
+ */
+ // 开始AI思考动画
+ startAIThinking() {
+ this.isAIThinking = true
+ this.isAISpeaking = false
+ },
+
+ // 开始AI说话动画
+ startAISpeaking() {
+ this.isAIThinking = false
+ this.isAISpeaking = true
+
+ // 清除之前的说话定时器
+ if (this.speakingTimer) {
+ clearTimeout(this.speakingTimer)
+ }
+ },
+
+ // 停止AI动画
+ stopAIAnimation() {
+ this.isAIThinking = false
+ this.isAISpeaking = false
+
+ if (this.speakingTimer) {
+ clearTimeout(this.speakingTimer)
+ this.speakingTimer = null
+ }
+ },
+
+ // 眨眼动画
+ triggerBlink() {
+ if (this.isBlinking) return
+
+ this.isBlinking = true
+ setTimeout(() => {
+ this.isBlinking = false
+ }, 150)
+ },
+
+ // 启动自动眨眼
+ startAutoBlinking() {
+ if (this.blinkTimer) {
+ clearInterval(this.blinkTimer)
+ }
+
+ this.blinkTimer = setInterval(() => {
+ if (!this.isDestroyed) {
+ this.triggerBlink()
+ }
+ }, 3000 + Math.random() * 2000) // 3-5秒随机眨眼
+ },
+
+ // 停止自动眨眼
+ stopAutoBlinking() {
+ if (this.blinkTimer) {
+ clearInterval(this.blinkTimer)
+ this.blinkTimer = null
+ }
+ },
+
+ // 启动鼠标跟踪
+ 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)`
+ }
},
/**
@@ -1000,13 +1395,25 @@ export default {
},
/**
- * 获取分组的引用来源
+ * 获取分组的引用来源(带缓存优化)
*/
getGroupedReferences(resources) {
if (!resources || !Array.isArray(resources)) {
return []
}
+ // 生成缓存键
+ const cacheKey = JSON.stringify(resources.map(r => ({
+ document_name: r.document_name,
+ id: r.id || r.chunk_id
+ })))
+
+ // 检查缓存
+ if (this.referencesCache.has(cacheKey)) {
+ return this.referencesCache.get(cacheKey)
+ }
+
+ // 计算分组
const grouped = {}
resources.forEach(resource => {
const docName = resource.document_name || '未知文档'
@@ -1016,10 +1423,20 @@ export default {
grouped[docName].push(resource)
})
- return Object.entries(grouped).map(([docName, items]) => ({
+ const result = Object.entries(grouped).map(([docName, items]) => ({
docName,
items
}))
+
+ // 缓存结果
+ if (this.referencesCache.size >= this.maxReferencesCacheSize) {
+ // 删除最旧的缓存条目
+ const firstKey = this.referencesCache.keys().next().value
+ this.referencesCache.delete(firstKey)
+ }
+ this.referencesCache.set(cacheKey, result)
+
+ return result
},
/**
@@ -1075,12 +1492,12 @@ export default {
this.memoryCheckInterval = setInterval(() => {
if (performance.memory) {
const memoryUsage = performance.memory.usedJSHeapSize / 1024 / 1024 // MB
-
+
// 如果内存使用超过100MB,进行清理
if (memoryUsage > 100) {
this.performMemoryCleanup()
}
-
+
this.lastMemoryUsage = memoryUsage
}
}, 30000) // 每30秒检查一次
@@ -1088,7 +1505,7 @@ export default {
// 监控DOM节点数量
this.performanceMonitor = setInterval(() => {
const messageCount = this.messages.length
-
+
// 如果消息数量过多,清理旧消息
if (messageCount > 200) {
this.cleanupOldMessages()
@@ -1104,7 +1521,7 @@ export default {
clearInterval(this.memoryCheckInterval)
this.memoryCheckInterval = null
}
-
+
if (this.performanceMonitor) {
clearInterval(this.performanceMonitor)
this.performanceMonitor = null
@@ -1118,7 +1535,7 @@ export default {
if (this.requestMonitorInterval) {
clearInterval(this.requestMonitorInterval)
}
-
+
this.requestMonitorInterval = setInterval(() => {
this.checkActiveRequests()
}, 5000) // 每5秒检查一次
@@ -1132,14 +1549,14 @@ export default {
clearInterval(this.requestMonitorInterval)
this.requestMonitorInterval = null
}
-
+
// 取消所有活跃请求
this.activeRequests.forEach((request, requestId) => {
if (request.cancel) {
request.cancel('组件销毁')
}
})
-
+
this.activeRequests.clear()
},
@@ -1149,26 +1566,26 @@ export default {
checkActiveRequests() {
const now = Date.now()
const expiredRequests = []
-
+
this.activeRequests.forEach((request, requestId) => {
const duration = now - request.startTime
-
+
// 如果请求超过最大持续时间,标记为过期
if (duration > this.maxRequestDuration) {
expiredRequests.push({ requestId, request })
}
})
-
+
// 取消过期请求
expiredRequests.forEach(({ requestId, request }) => {
console.warn(`强制取消超时请求: ${request.type}, 持续时间: ${(Date.now() - request.startTime) / 1000}秒`)
-
+
if (request.cancel) {
request.cancel('请求超时被强制取消')
}
-
+
this.activeRequests.delete(requestId)
-
+
// 显示超时提示
this.showToast('请求超时已自动取消,请重试', 'warning')
})
@@ -1181,12 +1598,12 @@ export default {
try {
// 清理引用展示状态
this.showSingleReference = {}
-
+
// 强制垃圾回收(如果浏览器支持)
if (window.gc) {
window.gc()
}
-
+
console.log('执行内存清理')
} catch (error) {
console.warn('内存清理失败:', error)
@@ -1202,15 +1619,15 @@ export default {
if (this.messages.length > 100) {
const keepCount = 100
const removedCount = this.messages.length - keepCount
-
+
this.messages = this.messages.slice(-keepCount)
-
+
// 更新最早消息ID
const userMessages = this.messages.filter(msg => msg.sender === 'user')
if (userMessages.length > 0) {
this.earliestMessageId = userMessages[0].messageId
}
-
+
console.log(`清理了 ${removedCount} 条旧消息`)
}
} catch (error) {
@@ -1224,10 +1641,10 @@ export default {
setupErrorHandling() {
// 捕获未处理的Promise错误
window.addEventListener('unhandledrejection', this.handleUnhandledRejection)
-
+
// 捕获全局JavaScript错误
window.addEventListener('error', this.handleGlobalError)
-
+
// Vue错误处理
this.$options.errorCaptured = this.handleVueError
},
@@ -1237,10 +1654,10 @@ export default {
*/
handleUnhandledRejection(event) {
console.error('未处理的Promise错误:', event.reason)
-
+
// 防止错误冒泡导致页面崩溃
event.preventDefault()
-
+
// 如果是网络错误,显示友好提示
if (event.reason && (event.reason.message || '').includes('Network')) {
this.showToast('网络连接异常,请检查网络设置', 'error')
@@ -1254,13 +1671,13 @@ export default {
*/
handleGlobalError(event) {
console.error('全局JavaScript错误:', event.error)
-
+
// 防止错误导致页面白屏
event.preventDefault()
-
+
// 如果是内存相关错误,执行清理
- if (event.error && event.error.message &&
- (event.error.message.includes('memory') || event.error.message.includes('Maximum call stack'))) {
+ if (event.error && event.error.message &&
+ (event.error.message.includes('memory') || event.error.message.includes('Maximum call stack'))) {
this.performMemoryCleanup()
this.showToast('系统资源不足,已自动清理', 'warning')
}
@@ -1271,14 +1688,14 @@ export default {
*/
handleVueError(err, instance, info) {
console.error('Vue组件错误:', err, info)
-
+
// 如果是渲染错误,尝试重置状态
if (info && info.includes('render')) {
this.$nextTick(() => {
this.$forceUpdate()
})
}
-
+
return false // 阻止错误继续传播
}
}
@@ -1289,10 +1706,10 @@ export default {
/* 聊天弹窗容器 */
.chat-popup {
position: fixed;
- bottom: 45px;
- right: 60px;
- width: 400px;
- height: 600px;
+ bottom: 9vh;
+ right: 5vw;
+ width: 40vw;
+ height: 80vh;
background: #fff;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 8px 32px rgba(0, 0, 0, 0.1);
@@ -1340,11 +1757,251 @@ export default {
}
}
+/* 导航栏左侧容器 */
+.nav-left {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
.nav-title {
font-size: 16px;
font-weight: 600;
}
+/* 数字人头像容器 */
+.digital-avatar {
+ position: relative;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ transition: all 0.3s ease;
+ cursor: pointer;
+}
+
+.digital-avatar:hover {
+ transform: scale(1.05);
+}
+
+.digital-avatar.thinking {
+ animation: thinking-pulse 2s infinite;
+}
+
+.digital-avatar.thinking .eye {
+ animation: thinking-eyes 3s infinite;
+}
+
+.digital-avatar.thinking .avatar-face::before {
+ content: '';
+ position: absolute;
+ top: 8px;
+ left: 6px;
+ width: 8px;
+ height: 2px;
+ background: #333;
+ border-radius: 1px;
+ animation: thinking-eyebrow-left 2.5s infinite;
+}
+
+.digital-avatar.thinking .avatar-face::after {
+ content: '';
+ position: absolute;
+ top: 8px;
+ right: 6px;
+ width: 8px;
+ height: 2px;
+ background: #333;
+ border-radius: 1px;
+ animation: thinking-eyebrow-right 2.5s infinite;
+}
+
+.digital-avatar.thinking .avatar-mouth {
+ animation: thinking-mouth 4s infinite;
+ transform-origin: center;
+}
+
+.digital-avatar.speaking {
+ animation: speaking-glow 0.5s infinite alternate;
+}
+
+/* 头像发光效果 */
+.avatar-glow {
+ position: absolute;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+ opacity: 0;
+ z-index: -1;
+ transition: opacity 0.3s ease;
+}
+
+.digital-avatar.speaking .avatar-glow {
+ opacity: 0.6;
+ animation: glow-pulse 1s infinite;
+}
+
+/* 头像面部 */
+.avatar-face {
+ position: relative;
+ width: 32px;
+ height: 32px;
+ background: #fff;
+ border-radius: 50%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+/* 眼睛容器 */
+.avatar-eyes {
+ display: flex;
+ gap: 6px;
+ margin-bottom: 4px;
+}
+
+/* 眼睛 */
+.eye {
+ width: 4px;
+ height: 4px;
+ background: #333;
+ border-radius: 50%;
+ transition: height 0.15s ease, background 0.15s ease, transform 0.1s ease-out;
+ position: relative;
+ transform-origin: center;
+}
+
+.eye.blink {
+ height: 1px;
+ background: #666;
+}
+
+/* 嘴巴 */
+.avatar-mouth {
+ width: 8px;
+ height: 3px;
+ background: #333;
+ border-radius: 0 0 8px 8px;
+ transition: all 0.2s ease;
+}
+
+.avatar-mouth.talking {
+ animation: mouth-talk 0.3s infinite alternate;
+}
+
+/* 动画定义 */
+@keyframes thinking-pulse {
+ 0%, 100% {
+ box-shadow: 0 0 0 0 rgba(79, 172, 254, 0.4);
+ }
+ 50% {
+ box-shadow: 0 0 0 8px rgba(79, 172, 254, 0);
+ }
+}
+
+@keyframes thinking-eyes {
+ 0%, 100% {
+ transform: translateX(0) translateY(0) scale(1);
+ }
+ 25% {
+ transform: translateX(-1px) translateY(-0.5px) scale(0.95);
+ }
+ 50% {
+ transform: translateX(1px) translateY(0.5px) scale(0.9);
+ }
+ 75% {
+ transform: translateX(0) translateY(-1px) scale(0.95);
+ }
+}
+
+@keyframes thinking-eyebrow-left {
+ 0%, 100% {
+ transform: translateY(0) rotate(0deg);
+ opacity: 0.7;
+ }
+ 30% {
+ transform: translateY(-1px) rotate(-2deg);
+ opacity: 0.9;
+ }
+ 60% {
+ transform: translateY(0.5px) rotate(1deg);
+ opacity: 0.8;
+ }
+}
+
+@keyframes thinking-eyebrow-right {
+ 0%, 100% {
+ transform: translateY(0) rotate(0deg);
+ opacity: 0.7;
+ }
+ 35% {
+ transform: translateY(-0.5px) rotate(2deg);
+ opacity: 0.9;
+ }
+ 65% {
+ transform: translateY(1px) rotate(-1deg);
+ opacity: 0.8;
+ }
+}
+
+@keyframes thinking-mouth {
+ 0%, 100% {
+ transform: translateX(0) scale(1);
+ border-radius: 50%;
+ }
+ 25% {
+ transform: translateX(-1px) scale(0.8);
+ border-radius: 30% 70% 70% 30%;
+ }
+ 50% {
+ transform: translateX(1px) scale(0.9);
+ border-radius: 70% 30% 30% 70%;
+ }
+ 75% {
+ transform: translateX(0) scale(0.85);
+ border-radius: 40% 60% 60% 40%;
+ }
+}
+
+@keyframes speaking-glow {
+ 0% {
+ box-shadow: 0 0 10px rgba(79, 172, 254, 0.5);
+ }
+ 100% {
+ box-shadow: 0 0 20px rgba(79, 172, 254, 0.8);
+ }
+}
+
+@keyframes glow-pulse {
+ 0%, 100% {
+ transform: scale(1);
+ opacity: 0.6;
+ }
+ 50% {
+ transform: scale(1.1);
+ opacity: 0.8;
+ }
+}
+
+@keyframes mouth-talk {
+ 0% {
+ height: 3px;
+ width: 8px;
+ }
+ 100% {
+ height: 6px;
+ width: 10px;
+ }
+}
+
.nav-close {
font-size: 24px;
cursor: pointer;
@@ -1972,6 +2629,28 @@ export default {
color: #722ed1 !important;
}
+/* 消息限制提示样式 */
+.message-limit-notice {
+ background: #f6f8fa;
+ border: 1px solid #e1e4e8;
+ border-radius: 8px;
+ padding: 12px 16px;
+ margin: 16px 0;
+ text-align: center;
+}
+
+.notice-text {
+ color: #586069;
+ font-size: 14px;
+ font-weight: 500;
+ margin-bottom: 4px;
+}
+
+.notice-subtext {
+ color: #959da5;
+ font-size: 12px;
+}
+
/* 响应式设计 */
@media (max-width: 768px) {
.chat-popup {
diff --git a/src/layout/index.vue b/src/layout/index.vue
index 336810e..34a71f0 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -21,7 +21,7 @@
- AI
+ AI
@@ -192,22 +192,7 @@ export default {
}
-// ai悬停
-.ai-hover {
- position: fixed;
- bottom: 20px;
- right: 20px;
- z-index: 999;
- width: 50px;
- height: 50px;
- border-radius: 50%;
- background-color: #409eff;
- color: #fff;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
-}
+
//AI
.ai-hover {
@@ -215,8 +200,8 @@ export default {
right: 20px;
bottom: 20px;
/* 和弹窗拉开距离 */
- width: 40px;
- height: 40px;
+ width: 5vw;
+ height: 7vh;
background-color: #409eff;
border-radius: 50%;
display: flex;
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index f1d87e8..72ae595 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -3,6 +3,7 @@ import {
getTokenKeySessionStorage,
removeToken,
setTokenKeySessionStorage,
+ clearAllUserCache,
} from "@/utils/auth";
const user = {
@@ -92,7 +93,9 @@ const user = {
commit("SET_TOKEN", "");
commit("SET_ROLES", []);
commit("SET_PERMISSIONS", []);
- removeToken();
+ commit("SET_USERINFO", {});
+ // 清理所有用户相关的缓存数据,包括conversation_id
+ clearAllUserCache();
resolve();
})
.catch((error) => {
@@ -105,7 +108,11 @@ const user = {
FedLogOut({ commit }) {
return new Promise((resolve) => {
commit("SET_TOKEN", "");
- removeToken();
+ commit("SET_ROLES", []);
+ commit("SET_PERMISSIONS", []);
+ commit("SET_USERINFO", {});
+ // 清理所有用户相关的缓存数据,包括conversation_id
+ clearAllUserCache();
resolve();
});
},
diff --git a/src/utils/ai_request.js b/src/utils/ai_request.js
index 5b387b4..b1fced2 100644
--- a/src/utils/ai_request.js
+++ b/src/utils/ai_request.js
@@ -5,7 +5,8 @@ import { showToast } from "@/utils/toast"; // 请替换为你的Toast组件
// 创建axios实例
const service = axios.create({
- baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088',
+ // baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088',
+ baseURL: process.env.VUE_APP_BASE_API,
timeout: 15000,
headers: {
"Content-Type": "application/json",
diff --git a/src/utils/auth.js b/src/utils/auth.js
index 34a5d8e..2fe05d8 100644
--- a/src/utils/auth.js
+++ b/src/utils/auth.js
@@ -23,3 +23,28 @@ export function getTokenKeySessionStorage() {
export function removeToken() {
return Cookies.remove(TokenKey)
}
+
+// 清理sessionStorage中的token
+export function removeTokenFromSessionStorage() {
+ sessionStorage.removeItem(TokenKey)
+}
+
+// 清理AI聊天相关的localStorage数据
+export function clearAIChatCache() {
+ localStorage.removeItem('conversation_id')
+ // 可以根据需要添加其他AI聊天相关的缓存清理
+}
+
+// 清理所有用户相关的缓存数据
+export function clearAllUserCache() {
+ // 清理token相关
+ removeToken()
+ removeTokenFromSessionStorage()
+
+ // 清理AI聊天缓存
+ clearAIChatCache()
+
+ // 清理其他用户相关的localStorage数据
+ localStorage.removeItem('userId')
+ localStorage.removeItem('userName')
+}
diff --git a/src/views/Home/comps/dept-qgzx.vue b/src/views/Home/comps/dept-qgzx.vue
index dacf2d6..3f9a0b0 100644
--- a/src/views/Home/comps/dept-qgzx.vue
+++ b/src/views/Home/comps/dept-qgzx.vue
@@ -1,28 +1,34 @@
-
-
-
-
-
-
-
-
- {{ v.label }}·待办
-
-
- {{ v.value }}
-
-
-
更多☞
-
+
+
+
+
+
+
{{ v.label }}·待办
+
+ {{ v.value }}
+
+
更多☞
+
+
\ No newline at end of file
+
diff --git a/src/views/Home/comps/fdy-undo.vue b/src/views/Home/comps/fdy-undo.vue
index 5d45252..56f21e8 100644
--- a/src/views/Home/comps/fdy-undo.vue
+++ b/src/views/Home/comps/fdy-undo.vue
@@ -110,7 +110,28 @@ export default {
name: "knzzgl",
value: 0,
url: "/hard/gl/fdy"
- }
+ },
+ // 陈冠元
+ {
+ label: "辅导员·自治区人民政府奖学金审核",
+ name: "knzzzzq",
+ value: 0,
+ url: "/hard/zzq/fdy",
+ },
+ //知无涯
+ {
+ label: "辅导员·中职升高职补助审核",
+ name: "zsg",
+ value: 0,
+ url: "hard/zsg/fdy"
+ },
+ //邵政文
+ {
+ label: "辅导员·住宿费用确认审核",
+ name: "zsfy",
+ value: 0,
+ url: "/dormitory/new/FdyConfirm"
+ },
],
diff --git a/src/views/Home/comps/jwc-undo.vue b/src/views/Home/comps/jwc-undo.vue
index 5c7a43b..ce55b1d 100644
--- a/src/views/Home/comps/jwc-undo.vue
+++ b/src/views/Home/comps/jwc-undo.vue
@@ -1,195 +1,238 @@
-
-
-
-
-
-
- {{ v.label }}·待办
-
-
- {{ v.value }}
-
-
-
更多☞
-
+
+
+
+
+
+
{{ v.label }}·待办
+
+ {{ v.value }}
+
+
更多☞
+
+
diff --git a/src/views/Home/comps/sj-undo.vue b/src/views/Home/comps/sj-undo.vue
index 74d0550..5ebde00 100644
--- a/src/views/Home/comps/sj-undo.vue
+++ b/src/views/Home/comps/sj-undo.vue
@@ -1,132 +1,157 @@
-
-
-
-
-
-
- {{ v.label }}·待办
-
-
- {{ v.value }}
-
-
-
更多☞
-
+
+
+
+
+
+
{{ v.label }}·待办
+
+ {{ v.value }}
+
+
更多☞
+
+
\ No newline at end of file
+
diff --git a/src/views/Home/comps/xw-undo.vue b/src/views/Home/comps/xw-undo.vue
index d9c2dca..1145df1 100644
--- a/src/views/Home/comps/xw-undo.vue
+++ b/src/views/Home/comps/xw-undo.vue
@@ -95,6 +95,27 @@ export default {
value: 0,
url: "/hard/gl/xw",
},
+ // 陈冠元
+ {
+ label: "学务·自治区人民政府奖学金审核",
+ name: "knzzzzq",
+ value: 0,
+ url: "/hard/zzq/xw",
+ },
+ // 宁博
+ {
+ label: "学务·辅导员管理-成果绩效审核",
+ name: "cg",
+ value: 0,
+ url: "/teacher/achievement/achievementCheck",
+ },
+ // 陈冠元
+ {
+ label: "学务·辅导员管理-业绩考核",
+ name: "yj",
+ value: 0,
+ url: "/teacher/teacherKpiFilling/collegeAudit/XWAuditList",
+ },
],
};
},
diff --git a/src/views/Home/comps/zdls-qgzx.vue b/src/views/Home/comps/zdls-qgzx.vue
index ba71cef..bae19b9 100644
--- a/src/views/Home/comps/zdls-qgzx.vue
+++ b/src/views/Home/comps/zdls-qgzx.vue
@@ -70,6 +70,18 @@ export default {
value: data.workLog,
url: "/workstudy/worklog/zdls"
});
+ this.taskList.push({
+ label: "学工处长综合绩效审核",
+ name: "jx",
+ value: data.jx || 0,
+ url: "teacher/performance/studentW/director"
+ });
+ this.taskList.push({
+ label: "科室综合绩效复核",
+ name: "jx",
+ value: data.jx || 0,
+ url: "teacher/performance/studentW/department"
+ });
}
},
diff --git a/src/views/Home/index-new-blue.vue b/src/views/Home/index-new-blue.vue
index cfd9f9f..192c4b1 100644
--- a/src/views/Home/index-new-blue.vue
+++ b/src/views/Home/index-new-blue.vue
@@ -372,7 +372,7 @@
diff --git a/src/views/aitutor/psychological-earlywarning/index.vue b/src/views/aitutor/psychological-earlywarning/index.vue
deleted file mode 100644
index e69de29..0000000
diff --git a/src/views/comprehensive/identifytexs/index.vue b/src/views/comprehensive/identifytexs/index.vue
index 51d69a5..00bbf4d 100644
--- a/src/views/comprehensive/identifytexs/index.vue
+++ b/src/views/comprehensive/identifytexs/index.vue
@@ -4,7 +4,22 @@
-
+
+
+
+
+
+
+
@@ -479,7 +494,7 @@
@@ -472,4 +521,5 @@ export default {
margin-bottom: 5px;
}
}
-
\ No newline at end of file
+
+
diff --git a/src/views/dormitory/new/stuDom/JwcConfirm.vue b/src/views/dormitory/new/stuDom/JwcConfirm.vue
index 2707217..648d7f6 100644
--- a/src/views/dormitory/new/stuDom/JwcConfirm.vue
+++ b/src/views/dormitory/new/stuDom/JwcConfirm.vue
@@ -42,6 +42,7 @@
搜索
重置
一键确认所有辅导员已确认的记录
+ 一键确认未进行住宿费用确认的学生
@@ -152,7 +153,7 @@