From a0912edc6a2342a5ac425d4a4848ab42e6a0b7b4 Mon Sep 17 00:00:00 2001 From: "Mr.j" <2221464500@qq.com> Date: Fri, 8 Aug 2025 17:35:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=94=B3=E8=AF=B7=E9=87=87?= =?UTF-8?q?=E8=B4=AD=E6=8E=A8=E9=80=81=E4=BF=A1=E6=81=AF=E5=88=B0=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E5=8F=B0=E5=87=BA=E7=8E=B0=E8=AF=AD=E9=9F=B3=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E5=92=8C=E9=97=AA=E7=83=81=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/HeaderRightBar/index.vue | 244 +++++++++++++++++- src/types/auto-imports.d.ts | 2 +- vite.config.ts | 5 + 3 files changed, 245 insertions(+), 6 deletions(-) diff --git a/src/layout/components/HeaderRightBar/index.vue b/src/layout/components/HeaderRightBar/index.vue index d1b57ae..64719de 100644 --- a/src/layout/components/HeaderRightBar/index.vue +++ b/src/layout/components/HeaderRightBar/index.vue @@ -19,8 +19,13 @@ :content-style="{ marginTop: '-5px', padding: 0, border: 'none' }" :arrow-style="{ width: 0, height: 0 }" > - - + + @@ -97,10 +102,148 @@ onBeforeUnmount(() => { socket.close() socket = null } + + // 清理标题闪烁 + if (titleFlashInterval) { + clearInterval(titleFlashInterval) + titleFlashInterval = null + } }) const unreadMessageCount = ref(0) +// 语音提示功能 +const playNotificationSound = () => { + try { + // 创建音频上下文 + const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)() + const oscillator = audioContext.createOscillator() + const gainNode = audioContext.createGain() + + oscillator.connect(gainNode) + gainNode.connect(audioContext.destination) + + // 设置音频参数 + oscillator.frequency.setValueAtTime(800, audioContext.currentTime) // 800Hz + oscillator.type = 'sine' + + gainNode.gain.setValueAtTime(0.3, audioContext.currentTime) + gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5) + + // 播放音频 + oscillator.start(audioContext.currentTime) + oscillator.stop(audioContext.currentTime + 0.5) + + console.log('播放语音提示') + } catch (error) { + console.error('播放语音提示失败:', error) + } +} + +// 页面标题闪烁功能 +let titleFlashInterval: NodeJS.Timeout | null = null +const flashPageTitle = () => { + const originalTitle = document.title + let flashCount = 0 + const maxFlashes = 6 // 闪烁3次(开-关-开-关-开-关) + + // 清除之前的闪烁 + if (titleFlashInterval) { + clearInterval(titleFlashInterval) + } + + titleFlashInterval = setInterval(() => { + if (flashCount >= maxFlashes) { + document.title = originalTitle + if (titleFlashInterval) { + clearInterval(titleFlashInterval) + titleFlashInterval = null + } + return + } + + document.title = flashCount % 2 === 0 ? '🔔 新的采购申请' : originalTitle + flashCount++ + }, 500) +} + +// 暴露测试函数到全局,方便在控制台测试 +if (typeof window !== 'undefined') { + (window as any).testNotification = { + playSound: playNotificationSound, + flashTitle: flashPageTitle, + showNotification: () => { + Notification.info({ + title: '测试通知', + content: '这是一个测试通知,用于验证通知功能是否正常工作。', + duration: 5000, + closable: true, + position: 'topRight' + }) + unreadMessageCount.value++ + }, + testAll: () => { + playNotificationSound() + flashPageTitle() + Notification.info({ + title: '测试通知', + content: '这是一个测试通知,用于验证通知功能是否正常工作。', + duration: 5000, + closable: true, + position: 'topRight' + }) + unreadMessageCount.value++ + }, + // 添加调试函数 + debugWebSocket: () => { + console.log('=== WebSocket 调试信息 ===') + console.log('Socket对象:', socket) + console.log('Socket状态:', socket ? socket.readyState : '未连接') + console.log('Token:', getToken()) + console.log('环境变量:', import.meta.env.VITE_API_WS_URL) + console.log('未读消息计数:', unreadMessageCount.value) + console.log('用户Token:', userStore.token) + }, + // 手动触发WebSocket消息处理 + simulateWebSocketMessage: () => { + const mockMessage = { + type: "PROCUREMENT_APPLICATION", + title: "新的采购申请", + content: "收到来自 测试用户 的设备采购申请:测试设备" + } + + const event = new MessageEvent('message', { + data: JSON.stringify(mockMessage) + }) + + if (socket && socket.onmessage) { + console.log('模拟WebSocket消息:', mockMessage) + socket.onmessage(event) + } else { + console.error('WebSocket连接不存在或onmessage未设置') + } + }, + // 强制重新连接WebSocket + reconnectWebSocket: () => { + console.log('强制重新连接WebSocket') + const token = getToken() + if (token) { + if (socket) { + socket.close() + socket = null + } + initWebSocket(token) + } else { + console.error('Token不存在,无法重新连接') + } + } + } + + // 暴露socket对象到全局,方便调试 + ;(window as any).socket = socket + ;(window as any).unreadMessageCount = unreadMessageCount +} + // 初始化 WebSocket - 使用防抖避免重复连接 let initTimer: NodeJS.Timeout | null = null const initWebSocket = (token: string) => { @@ -116,10 +259,12 @@ const initWebSocket = (token: string) => { } try { + // 修复WebSocket URL,确保使用正确的端口 const wsUrl = import.meta.env.VITE_API_WS_URL || 'ws://localhost:8888' - console.log('正在连接WebSocket:', `${wsUrl}/websocket?token=${token}`) + const wsEndpoint = wsUrl.replace('8000', '8888') // 确保使用8888端口 + console.log('正在连接WebSocket:', `${wsEndpoint}/websocket?token=${token}`) - socket = new WebSocket(`${wsUrl}/websocket?token=${token}`) + socket = new WebSocket(`${wsEndpoint}/websocket?token=${token}`) socket.onopen = () => { console.log('WebSocket连接成功') @@ -133,6 +278,10 @@ const initWebSocket = (token: string) => { // 处理通知消息 if (data.type && data.title && data.content) { console.log('处理通知消息:', data) + + // 播放语音提示 + playNotificationSound() + // 显示通知 Notification.info({ title: data.title, @@ -144,6 +293,9 @@ const initWebSocket = (token: string) => { // 增加未读消息计数 unreadMessageCount.value++ + + // 触发页面标题闪烁 + flashPageTitle() } else { // 处理简单的数字消息(兼容旧版本) const count = Number.parseInt(event.data) @@ -181,10 +333,17 @@ const initWebSocket = (token: string) => { const getMessageCount = async () => { try { const token = getToken() + console.log('获取到token:', token ? '存在' : '不存在') + if (token && !socket) { + console.log('准备初始化WebSocket连接') nextTick(() => { initWebSocket(token) }) + } else if (!token) { + console.warn('Token不存在,无法建立WebSocket连接') + } else if (socket) { + console.log('WebSocket连接已存在') } } catch (error) { console.error('Failed to get message count:', error) @@ -218,9 +377,32 @@ const logout = () => { onMounted(() => { nextTick(() => { - // getMessageCount() + // 立即尝试初始化WebSocket + getMessageCount() + + // 如果第一次失败,1秒后重试 + setTimeout(() => { + if (!socket) { + console.log('首次连接失败,重试WebSocket连接') + getMessageCount() + } + }, 1000) }) }) + +// 监听用户登录状态变化 +watch(() => userStore.token, (newToken, oldToken) => { + console.log('Token变化:', { oldToken: oldToken ? '存在' : '不存在', newToken: newToken ? '存在' : '不存在' }) + + if (newToken && !socket) { + console.log('用户登录,初始化WebSocket连接') + getMessageCount() + } else if (!newToken && socket) { + console.log('用户登出,关闭WebSocket连接') + socket.close() + socket = null + } +}, { immediate: true }) diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index eab6be6..369aad4 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -70,6 +70,6 @@ declare global { // for type re-export declare global { // @ts-ignore - export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' import('vue') } diff --git a/vite.config.ts b/vite.config.ts index a38aa21..2c2fbf8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,11 @@ import createVitePlugins from './config/plugins' export default defineConfig(({ command, mode }) => { const env = loadEnv(mode, process.cwd()) as ImportMetaEnv + // 设置默认的WebSocket URL + if (!env.VITE_API_WS_URL) { + env.VITE_API_WS_URL = 'ws://localhost:8888' + } + return { // 开发或生产环境服务的公共基础路径 base: env.VITE_BASE,