Compare commits
No commits in common. "d5929bdc2d81d8c90b524c998499e62e753b8440" and "0401a280374d24a6a1f65dcc75ad42900a76491a" have entirely different histories.
d5929bdc2d
...
0401a28037
|
@ -19,13 +19,8 @@
|
||||||
:content-style="{ marginTop: '-5px', padding: 0, border: 'none' }"
|
:content-style="{ marginTop: '-5px', padding: 0, border: 'none' }"
|
||||||
:arrow-style="{ width: 0, height: 0 }"
|
:arrow-style="{ width: 0, height: 0 }"
|
||||||
>
|
>
|
||||||
<a-badge
|
<a-badge :count="unreadMessageCount" dot>
|
||||||
:count="unreadMessageCount"
|
<a-button size="mini" class="gi_hover_btn">
|
||||||
:dot="unreadMessageCount > 0"
|
|
||||||
:show-zero="false"
|
|
||||||
class="notification-badge"
|
|
||||||
>
|
|
||||||
<a-button size="mini" class="gi_hover_btn notification-btn">
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-notification :size="18" />
|
<icon-notification :size="18" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -102,148 +97,10 @@ onBeforeUnmount(() => {
|
||||||
socket.close()
|
socket.close()
|
||||||
socket = null
|
socket = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理标题闪烁
|
|
||||||
if (titleFlashInterval) {
|
|
||||||
clearInterval(titleFlashInterval)
|
|
||||||
titleFlashInterval = null
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const unreadMessageCount = ref(0)
|
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 - 使用防抖避免重复连接
|
// 初始化 WebSocket - 使用防抖避免重复连接
|
||||||
let initTimer: NodeJS.Timeout | null = null
|
let initTimer: NodeJS.Timeout | null = null
|
||||||
const initWebSocket = (token: string) => {
|
const initWebSocket = (token: string) => {
|
||||||
|
@ -259,12 +116,10 @@ const initWebSocket = (token: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 修复WebSocket URL,确保使用正确的端口
|
|
||||||
const wsUrl = import.meta.env.VITE_API_WS_URL || 'ws://localhost:8888'
|
const wsUrl = import.meta.env.VITE_API_WS_URL || 'ws://localhost:8888'
|
||||||
const wsEndpoint = wsUrl.replace('8000', '8888') // 确保使用8888端口
|
console.log('正在连接WebSocket:', `${wsUrl}/websocket?token=${token}`)
|
||||||
console.log('正在连接WebSocket:', `${wsEndpoint}/websocket?token=${token}`)
|
|
||||||
|
|
||||||
socket = new WebSocket(`${wsEndpoint}/websocket?token=${token}`)
|
socket = new WebSocket(`${wsUrl}/websocket?token=${token}`)
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
console.log('WebSocket连接成功')
|
console.log('WebSocket连接成功')
|
||||||
|
@ -278,10 +133,6 @@ const initWebSocket = (token: string) => {
|
||||||
// 处理通知消息
|
// 处理通知消息
|
||||||
if (data.type && data.title && data.content) {
|
if (data.type && data.title && data.content) {
|
||||||
console.log('处理通知消息:', data)
|
console.log('处理通知消息:', data)
|
||||||
|
|
||||||
// 播放语音提示
|
|
||||||
playNotificationSound()
|
|
||||||
|
|
||||||
// 显示通知
|
// 显示通知
|
||||||
Notification.info({
|
Notification.info({
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
@ -293,9 +144,6 @@ const initWebSocket = (token: string) => {
|
||||||
|
|
||||||
// 增加未读消息计数
|
// 增加未读消息计数
|
||||||
unreadMessageCount.value++
|
unreadMessageCount.value++
|
||||||
|
|
||||||
// 触发页面标题闪烁
|
|
||||||
flashPageTitle()
|
|
||||||
} else {
|
} else {
|
||||||
// 处理简单的数字消息(兼容旧版本)
|
// 处理简单的数字消息(兼容旧版本)
|
||||||
const count = Number.parseInt(event.data)
|
const count = Number.parseInt(event.data)
|
||||||
|
@ -333,17 +181,10 @@ const initWebSocket = (token: string) => {
|
||||||
const getMessageCount = async () => {
|
const getMessageCount = async () => {
|
||||||
try {
|
try {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
console.log('获取到token:', token ? '存在' : '不存在')
|
|
||||||
|
|
||||||
if (token && !socket) {
|
if (token && !socket) {
|
||||||
console.log('准备初始化WebSocket连接')
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
initWebSocket(token)
|
initWebSocket(token)
|
||||||
})
|
})
|
||||||
} else if (!token) {
|
|
||||||
console.warn('Token不存在,无法建立WebSocket连接')
|
|
||||||
} else if (socket) {
|
|
||||||
console.log('WebSocket连接已存在')
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get message count:', error)
|
console.error('Failed to get message count:', error)
|
||||||
|
@ -377,32 +218,9 @@ const logout = () => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 立即尝试初始化WebSocket
|
// getMessageCount()
|
||||||
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 })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
@ -424,56 +242,4 @@ watch(() => userStore.token, (newToken, oldToken) => {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通知徽章样式
|
|
||||||
.notification-badge {
|
|
||||||
.arco-badge-dot {
|
|
||||||
background-color: #f53f3f;
|
|
||||||
box-shadow: 0 0 0 2px rgba(245, 63, 63, 0.2);
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arco-badge-count {
|
|
||||||
background-color: #f53f3f;
|
|
||||||
font-weight: bold;
|
|
||||||
animation: bounce 0.6s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-btn {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 脉冲动画
|
|
||||||
@keyframes pulse {
|
|
||||||
0% {
|
|
||||||
box-shadow: 0 0 0 0 rgba(245, 63, 63, 0.7);
|
|
||||||
}
|
|
||||||
70% {
|
|
||||||
box-shadow: 0 0 0 10px rgba(245, 63, 63, 0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 0 0 rgba(245, 63, 63, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 弹跳动画
|
|
||||||
@keyframes bounce {
|
|
||||||
0%, 20%, 53%, 80%, 100% {
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
}
|
|
||||||
40%, 43% {
|
|
||||||
transform: translate3d(0, -8px, 0);
|
|
||||||
}
|
|
||||||
70% {
|
|
||||||
transform: translate3d(0, -4px, 0);
|
|
||||||
}
|
|
||||||
90% {
|
|
||||||
transform: translate3d(0, -2px, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,11 +5,6 @@ import createVitePlugins from './config/plugins'
|
||||||
export default defineConfig(({ command, mode }) => {
|
export default defineConfig(({ command, mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd()) as ImportMetaEnv
|
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 {
|
return {
|
||||||
// 开发或生产环境服务的公共基础路径
|
// 开发或生产环境服务的公共基础路径
|
||||||
base: env.VITE_BASE,
|
base: env.VITE_BASE,
|
||||||
|
|
Loading…
Reference in New Issue